<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>Jesse Liberty - Silverlight Geek</title>
	<atom:link href="https://jesseliberty.com/feed/" rel="self" type="application/rss+xml"/>
	<link>https://jesseliberty.com</link>
	<description>More Signal - Less Noise</description>
	<lastBuildDate>Sat, 10 Oct 2026 23:02:50 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://jesseliberty.com/wp-content/uploads/2026/07/cropped-Square-Headshot-32x32.jpg</url>
	<title>Jesse Liberty</title>
	<link>https://jesseliberty.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Creating a Spec Kit Extension</title>
		<link>https://jesseliberty.com/2026/10/10/creating-a-spec-kit-extension/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Sat, 10 Oct 2026 18:08:53 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13857</guid>

					<description><![CDATA[Want to build an extension to Spec Kit? This comprehensive guide walks you through everything you need: a ready-to-drop extension layout, a complete extension.yml manifest, a slash-command file, a lifecycle hook that runs a C# helper, a full C# console &#8230; <a href="https://jesseliberty.com/2026/10/10/creating-a-spec-kit-extension/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Want to build an extension to Spec Kit? This comprehensive guide walks you through everything you need: a ready-to-drop extension layout, a complete extension.yml manifest, a slash-command file, a lifecycle hook that runs a C# helper, a full C# console app (source + csproj), install scripts, CI to build multi-platform artifacts and package the extension, plus troubleshooting, testing, and security advice.</p>



<figure class="wp-block-image size-large is-resized"><img fetchpriority="high" decoding="async" width="800" height="799" src="https://jesseliberty.com/wp-content/uploads/2026/10/spec-kit-extension-2-800x799.jpg" alt="" class="wp-image-13864" style="aspect-ratio:1.001237565964601;width:483px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/spec-kit-extension-2-800x799.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/10/spec-kit-extension-2-150x150.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/10/spec-kit-extension-2-300x300.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/10/spec-kit-extension-2-768x767.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/10/spec-kit-extension-2.jpg 974w" sizes="(max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">What you’ll get (artifacts shown in this guide)</p>



<ul class="wp-block-list">
<li>A consistent extension folder layout you can copy into a project-level .specify/extensions/ directory for local testing. (If your environment uses a different extension directory, replace .specify accordingly.)</li>



<li>Full extension.yml manifest.</li>



<li>A slash command file: addon/commands/speckit.extn.myfeature.md.</li>



<li>A lifecycle hook descriptor: addon/hooks/after-plan.yml and platform run wrappers.</li>



<li>A small C# console app (MyFeatureRunner) that reads a spec path and writes structured JSON to stdout.</li>



<li>Install scripts (install.sh, install.ps1) and OS wrappers (run.sh/run.ps1) to select the right binary.</li>



<li>A GitHub Actions workflow that builds platform binaries and packages a ZIP for publishing.</li>



<li>Tests (xUnit) that exercise the analyzer logic (no Python anywhere).</li>
</ul>



<span id="more-13857"></span>



<p class="wp-block-paragraph"><strong>Step 0</strong> — conventions &amp; a note on paths</p>



<ul class="wp-block-list">
<li>I’ll use &#8220;Spec Kit&#8221; consistently here and show the local extension install path as .specify/extensions/ (this is the common convention in community examples). If your tooling uses a different folder, replace .specify accordingly.</li>



<li>Slash command namespace convention: /speckit.extn..* — follow this for discoverability.</li>



<li>Hooks use placeholders like {{spec.path}}; confirm your Spec Kit version’s exact interpolation tokens and adapt if necessary.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 1</strong> — pick scope and design<br />Decide the minimal valuable feature for your first release. Example here:</p>



<ul class="wp-block-list">
<li>Add a slash command /speckit.extn.myfeature that runs an analysis on the current spec and returns structured JSON with a summary and issues. Also register an after-plan lifecycle hook that runs the same analyzer automatically when planning completes.</li>
</ul>



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



<ul class="wp-block-list">
<li>Inputs: spec path (string), format (json|text), output path (optional).</li>



<li>Outputs: structured JSON (summary, issues[]), exit codes.</li>



<li>Failure modes: missing spec, parse errors, internal errors — map to clear exit codes and clear stderr messages.</li>
</ul>



<p class="wp-block-paragraph">Design outcome: a C# console app MyFeatureRunner that accepts &#8211;spec and writes JSON to stdout or an output file. Hooks call the executable with placeholders.</p>



<p class="wp-block-paragraph"><strong>Step 2</strong> — folder layout (concrete)<br />Create this structure (top-level folder is the extension package root):</p>



<p class="wp-block-paragraph">com.yourorg.myfeature/<br />extension.yml<br />README.md<br />addon/<br />commands/<br />speckit.extn.myfeature.md<br />hooks/<br />after-plan.yml<br />templates/<br />mytemplate.txt<br />bin/<br />linux/<br />MyFeatureRunner &lt;&#8211; published single-file linux binary<br />macos/<br />MyFeatureRunner &lt;&#8211; published macos binary<br />windows/<br />MyFeatureRunner.exe &lt;&#8211; published windows exe<br />run.sh &lt;&#8211; wrapper selecting right binary on Unix<br />run.ps1 &lt;&#8211; wrapper for Windows<br />src/<br />MyFeatureRunner/ &lt;&#8211; dotnet project (Program.cs, Analyzer.cs, MyFeatureRunner.csproj)<br />tests/<br />MyFeatureRunner.Tests/ &lt;&#8211; xUnit tests<br />examples/<br />sample-spec.md<br />install.sh<br />install.ps1</p>



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



<ul class="wp-block-list">
<li>Place platform-specific published binaries under addon/bin//. Include wrapper scripts (run.sh, run.ps1) that forward args to the right binary.</li>



<li>The extension.yml manifest (next) will map addon/ directories into the install target.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 3</strong> — extension manifest (full example)<br />Put this file at the extension root as extension.yml:</p>



<p class="wp-block-paragraph">name: com.yourorg.myfeature<br />version: 0.1.0<br />description: &#8220;Adds /speckit.extn.myfeature — analyzes a spec and returns structured JSON with issues.&#8221;<br />authors:</p>



<ul class="wp-block-list">
<li>name: Your Name<br />email: <a href="mailto:you@example.com">you@example.com</a><br />license: MIT<br />tags:</li>



<li>spec-analysis</li>



<li>speckit<br />install:<br />files:
<ul class="wp-block-list">
<li>src: addon/commands/<br />dest: commands/</li>



<li>src: addon/hooks/<br />dest: hooks/</li>



<li>src: addon/templates/<br />dest: templates/</li>



<li>src: addon/bin/<br />dest: bin/<br />hooks:</li>
</ul>
</li>



<li>name: after-plan<br />file: addon/hooks/after-plan.yml</li>
</ul>



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



<ul class="wp-block-list">
<li>install.files tells the Spec Kit installer how to copy files into the runtime extension directory. Keep the paths relative to the extension archive root.</li>



<li>hooks are entry points to a hook descriptor file (next).</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 4</strong> — slash command file (full example)<br />File: addon/commands/speckit.extn.myfeature.md</p>



<p class="wp-block-paragraph">Description: Analyze the given Spec file for structural issues and return a JSON report with &#8220;summary&#8221; and &#8220;issues&#8221;.<br />Inputs:</p>



<ul class="wp-block-list">
<li>spec-path: path to the spec file. Defaults to the current spec in the workspace if omitted.</li>



<li>format: &#8220;json&#8221; (default) or &#8220;text&#8221;.</li>



<li>output: optional path to write the JSON report.</li>
</ul>



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



<ul class="wp-block-list">
<li>result (JSON):<br />{<br />&#8220;summary&#8221;: &#8220;string&#8221;,<br />&#8220;issues&#8221;: [<br />{<br />&#8220;line&#8221;: 12,<br />&#8220;severity&#8221;: &#8220;warning&#8221;,<br />&#8220;ruleId&#8221;: &#8220;SP-001&#8221;,<br />&#8220;message&#8221;: &#8220;Example issue message&#8221;<br />}<br />]<br />}</li>
</ul>



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



<ul class="wp-block-list">
<li>/speckit.extn.myfeature spec-path=specs/login.spec format=json</li>



<li>/speckit.extn.myfeature format=text</li>
</ul>



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



<ul class="wp-block-list">
<li>When invoked, the agent will run the extension&#8217;s hook or run wrapper that calls the C# CLI with &#8211;spec . The CLI emits structured JSON to stdout. The agent should present JSON results or a text summary as requested.</li>
</ul>



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



<ul class="wp-block-list">
<li>Keep examples explicit and provide the JSON schema so integrators know how to parse the result.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 5</strong> — hook descriptor (full example)<br />File: addon/hooks/after-plan.yml</p>



<p class="wp-block-paragraph">hook: after-plan<br />run: ./bin/run.sh<br />args:</p>



<ul class="wp-block-list">
<li>&#8211;spec</li>



<li>&#8220;{{spec.path}}&#8221;</li>



<li>&#8211;output</li>



<li>&#8220;{{workspace}}/.specify/extensions/com.yourorg.myfeature/latest-report.json&#8221;<br />env:<br />LOG_LEVEL: info</li>
</ul>



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



<ul class="wp-block-list">
<li>run points to the wrapper script in addon/bin/ that selects the correct binary for the host OS. This simplifies cross-platform support.</li>



<li>args include placeholders. <strong>Confirm the exact placeholder names in your Spec Kit version</strong>; many installations support {{spec.path}} and {{workspace}} or similar fields.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 6</strong> — C# helper (full source example)<br />We’ll implement a small console app that performs simple analysis: counts characters and looks for TODO comments as &#8220;issues&#8221;. It’s intentionally simple but structured to be extended.</p>



<p class="wp-block-paragraph">Project: src/MyFeatureRunner/MyFeatureRunner.csproj Exe net8.0 enable enable</p>



<p class="wp-block-paragraph">Program.cs (src/MyFeatureRunner/Program.cs)</p>



<pre class="wp-block-code"><code>using System.Text.Json;
using System.Text.Json.Serialization;

public static class Program
{
  public static int Main(string&#91;] args)
  {
    var parsed = CliArgs.Parse(args);
    if (!parsed.IsValid)
    {
      Console.Error.WriteLine(parsed.ErrorMessage);
      return parsed.ExitCode;
    }</code></pre>



<pre class="wp-block-preformatted"> <code>   try<br />    {<br />        var report = Analyzer.AnalyzeFile(parsed.SpecPath);<br />        var options = new JsonSerializerOptions { WriteIndented = true };<br />        string json = JsonSerializer.Serialize(report, options);<br /><br />        if (!string.IsNullOrEmpty(parsed.Output))<br />        {<br />            File.WriteAllText(parsed.Output, json);<br />            Console.WriteLine($"Report written to {parsed.Output}");<br />        }<br />        else<br />        {<br />            Console.WriteLine(json);<br />        }<br /><br />        return 0;<br />    }<br />    catch (FileNotFoundException ex)<br />    {<br />        Console.Error.WriteLine(ex.Message);<br />        return 3;<br />    }<br />    catch (Exception ex)<br />    {<br />        Console.Error.WriteLine($"Internal error: {ex.Message}");<br />        return 10;<br />    }<br />}<br /></code>}</pre>



<p class="wp-block-paragraph">CliArgs helper (embedded in same file or separate file for clarity):</p>



<pre class="wp-block-code"><code>internal sealed class CliArgs
{
public string SpecPath { get; init; } = string.Empty;
public string Output { get; init; } = string.Empty;
public bool IsValid { get; init; }
public string ErrorMessage { get; init; } = string.Empty;
public int ExitCode { get; init; }
public static CliArgs Parse(string&#91;] args)
{
    if (args.Length == 0)
        return new CliArgs { IsValid = false, ErrorMessage = "Usage: MyFeatureRunner --spec &lt;path&gt; &#91;--output &lt;path&gt;]", ExitCode = 2 };

    var dict = new Dictionary&lt;string, string&gt;(StringComparer.OrdinalIgnoreCase);
    for (int i = 0; i &lt; args.Length; i++)
    {
        if (args&#91;i].StartsWith("--") &amp;&amp; i + 1 &lt; args.Length)
        {
            dict&#91;args&#91;i]] = args&#91;i + 1];
            i++;
        }
    }

    if (!dict.TryGetValue("--spec", out var spec) || string.IsNullOrWhiteSpace(spec))
        return new CliArgs { IsValid = false, ErrorMessage = "Missing required --spec &lt;path&gt;", ExitCode = 2 };

    dict.TryGetValue("--output", out var output);

    return new CliArgs { IsValid = true, SpecPath = spec!, Output = output ?? string.Empty };
}
}
</code></pre>



<p class="wp-block-paragraph">Analyzer (src/MyFeatureRunner/Analyzer.cs)</p>



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



<pre class="wp-block-preformatted">using System.Text.Json.Serialization;<br /><br />public static class Analyzer<br />{<br />  public static AnalysisReport AnalyzeFile(string path)<br />  {<br />  if (!File.Exists(path))<br />    throw new FileNotFoundException($"Spec not found: {path}", path);<code> </code><br />   <code>var text = File.ReadAllText(path);<br />    var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);<br /><br />    var issues = new List&lt;Issue&gt;();<br />    for (int i = 0; i &lt; lines.Length; i++)<br />    {<br />        var line = lines[i];<br />        if (line.Contains("TODO", StringComparison.OrdinalIgnoreCase))<br />        {<br />            issues.Add(new Issue<br />            {<br />                Line = i + 1,<br />                Severity = "warning",<br />                RuleId = "SP-001",<br />                Message = "Found TODO in spec; consider clarifying acceptance criteria."<br />            });<br />        }<br />    }<br /><br />    return new AnalysisReport<br />    {<br />        Summary = $"Spec has {text.Length} chars, {lines.Length} non-empty lines, {issues.Count} issues.",<br />        Issues = issues<br />    };<br />}<br /></code>}</pre>



<pre class="wp-block-code"><code>public class AnalysisReport
{
&#91;JsonPropertyName("summary")]
public string Summary { get; set; } = string.Empty;
&#91;JsonPropertyName("issues")]
public List&lt;Issue&gt; Issues { get; set; } = new();
}
</code></pre>



<pre class="wp-block-code"><code>public class Issue
{
&#91;JsonPropertyName("line")]
public int Line { get; set; }
&#91;JsonPropertyName("severity")]
public string Severity { get; set; } = string.Empty;
&#91;JsonPropertyName("ruleId")]
public string RuleId { get; set; } = string.Empty;
&#91;JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
}</code></pre>



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



<ul class="wp-block-list">
<li>Analyzer.AnalyzeFile is pure and easily testable. Keep the CLI thin.</li>



<li>Exit codes:
<ul class="wp-block-list">
<li>0 success</li>



<li>2 argument error / usage</li>



<li>3 not found (spec path)</li>



<li>10 internal error</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 7</strong> — unit tests (xUnit)<br />Create src/MyFeatureRunner.Tests/MyFeatureRunner.Tests.csproj and a simple test that calls Analyzer directly (no process spawning), ensuring repeatable logic.</p>



<p class="wp-block-paragraph">Sample test (src/MyFeatureRunner.Tests/AnalyzerTests.cs)</p>



<pre class="wp-block-code"><code>using Xunit;
using System.IO;
using System.Text.Json;

public class AnalyzerTests
{
  &#91;Fact]
  public void AnalyzeFile_ReturnsIssuesForTODO()
  {
    var tmp = Path.GetTempFileName();
    File.WriteAllText(tmp, "Line one\nTODO: add acceptance\nAnother line\n");
    var report = Analyzer.AnalyzeFile(tmp);
    Assert.Contains("Spec has", report.Summary);
    Assert.Single(report.Issues);
    Assert.Equal("SP-001", report.Issues&#91;0].RuleId);
  }
}</code></pre>



<p class="wp-block-paragraph"><strong>Step 8</strong> — run wrappers and permissions<br />Create addon/bin/run.sh (Unix wrapper):</p>



<pre class="wp-block-code"><code>#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" &amp;&amp; pwd)"
PLATFORM_BIN=""
if &#91;&#91; "$(uname -s)" == "Linux" ]]; then
PLATFORM_BIN="$ROOT/linux/MyFeatureRunner"
elif &#91;&#91; "$(uname -s)" == "Darwin" ]]; then
PLATFORM_BIN="$ROOT/macos/MyFeatureRunner"
else
PLATFORM_BIN="$ROOT/windows/MyFeatureRunner.exe"
fi
exec "$PLATFORM_BIN" "$@"</code></pre>



<p class="wp-block-paragraph">Make it executable: chmod +x addon/bin/run.sh.</p>



<p class="wp-block-paragraph">Windows wrapper addon/bin/run.ps1 (PowerShell):</p>



<pre class="wp-block-code"><code>param(&#91;Parameter(ValueFromRemainingArguments=$true)]$Args)
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$os = (Get-CimInstance -ClassName Win32_OperatingSystem).Caption
$exe = Join-Path $root "windows\MyFeatureRunner.exe"
&amp; $exe @Args</code></pre>



<p class="wp-block-paragraph"><strong>Step 9</strong> — installer scripts<br /></p>



<pre class="wp-block-code"><code>install.sh:
#!/usr/bin/env bash
set -euo pipefail
EXT_ID="com.yourorg.myfeature"
DEST=".specify/extensions/$EXT_ID"
mkdir -p "$DEST"
cp -r addon "$DEST/"
cp extension.yml "$DEST/"
cp README.md "$DEST/"
echo "Installed extension to $DEST"

install.ps1 (PowerShell):
param(&#91;string]$Dest = ".specify/extensions/com.yourorg.myfeature")
New-Item -ItemType Directory -Force -Path $Dest | Out-Null
Copy-Item -Path "addon*" -Destination $Dest -Recurse -Force
Copy-Item -Path "extension.yml" -Destination $Dest -Force
Copy-Item -Path "README.md" -Destination $Dest -Force
Write-Host "Installed extension to $Dest"</code></pre>



<p class="wp-block-paragraph"><strong>Step 10</strong> — test locally (step-by-step)</p>



<ol class="wp-block-list">
<li>In a Spec Kit project:<br />mkdir -p .specify/extensions<br />cp -r com.yourorg.myfeature .specify/extensions/</li>



<li>Confirm files:<br />ls .specify/extensions/com.yourorg.myfeature</li>



<li>Run the analyzer directly:<br />dotnet run &#8211;project src/MyFeatureRunner &#8212; &#8211;spec examples/sample-spec.md Expected JSON printed or &#8220;Report written to &#8221; if &#8211;output provided.</li>



<li>Trigger the after-plan hook via your normal Spec Kit flow (specify → plan) and confirm:
<ul class="wp-block-list">
<li>Hook runs and writes latest-report.json in the extension folder (or check STDOUT in logs).</li>



<li>If hook failed, inspect agent logs or the extension&#8217;s stderr captured by the CLI.</li>
</ul>
</li>
</ol>



<p class="wp-block-paragraph"><strong>Step 11</strong> — publishing &amp; packaging with CI (GitHub Actions)<br />A sample workflow .github/workflows/publish.yml that:</p>



<ul class="wp-block-list">
<li>Builds and publishes single-file binaries for multiple runtimes</li>



<li>Packages the extension folder into a zip</li>



<li>Uploads the zip as an artifact (or creates a release)</li>
</ul>



<p class="wp-block-paragraph">Sample YAML (abridged for clarity):</p>



<pre class="wp-block-code"><code>name: Build and Package Extension
on:
push:
tags:
- 'v*.<em>.</em>'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
runtime: &#91;linux-x64, linux-arm64, osx-arm64, win-x64]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Publish ${{ matrix.runtime }}
run: |
dotnet publish src/MyFeatureRunner -c Release -r ${{ matrix.runtime }}
-p:PublishSingleFile=true -p:PublishTrimmed=true -o ./addon/bin/${{ matrix.runtime }}
- name: Organize binaries
run: |
# move to the platform folder expected by the extension, e.g. linux-&gt;linux, osx-&gt;macos, win-&gt;windows
mkdir -p addon/bin/linux addon/bin/macos addon/bin/windows
if &#91;&#91; "${{ matrix.runtime }}" == linux* ]]; then mv ./addon/bin/${{ matrix.runtime }}/MyFeatureRunner ./addon/bin/linux/MyFeatureRunner; fi
if &#91;&#91; "${{ matrix.runtime }}" == osx* ]]; then mv ./addon/bin/${{ matrix.runtime }}/MyFeatureRunner ./addon/bin/macos/MyFeatureRunner; fi
if &#91;&#91; "${{ matrix.runtime }}" == win* ]]; then mv ./addon/bin/${{ matrix.runtime }}/MyFeatureRunner.exe ./addon/bin/windows/MyFeatureRunner.exe; fi
- name: Zip extension
if: ${{ matrix.runtime == 'linux-x64' }}
run: |
zip -r com.yourorg.myfeature-${{ github.ref_name }}.zip extension.yml addon README.md
- name: Upload package
uses: actions/upload-artifact@v4
with:
name: extension-zip
path: com.yourorg.myfeature-${{ github.ref_name }}.zip</code></pre>



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



<ul class="wp-block-list">
<li>This workflow demonstrates building multiple runtimes and packaging into a zip during the linux-x64 job; you can centralize packaging in a separate job that depends on build artifacts.</li>



<li>For production, consider signing artifacts and publishing to GitHub Releases or a private artifact store.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 12</strong> — catalog entry &amp; checksum<br />Create a catalog JSON entry referencing the ZIP URL and a checksum.</p>



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



<pre class="wp-block-code"><code>{
"id": "com.yourorg.myfeature",
"name": "My Feature",
"version": "0.1.0",
"url": "<a href="https://example.com/com.yourorg.myfeature-0.1.0.zip">https://example.com/com.yourorg.myfeature-0.1.0.zip</a>",
"checksum": "sha256:abcdef123456..."
}</code></pre>



<p class="wp-block-paragraph">Generate a checksum:</p>



<ul class="wp-block-list">
<li>Unix: sha256sum com.yourorg.myfeature-0.1.0.zip | awk &#8216;{print $1}&#8217;</li>



<li>PowerShell: (Get-FileHash .\com.yourorg.myfeature-0.1.0.zip -Algorithm SHA256).Hash</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 13</strong> — troubleshooting and debugging checklist</p>



<ul class="wp-block-list">
<li>Hook not executed:
<ul class="wp-block-list">
<li>Confirm extension.yml lists the hook and was installed to .specify/extensions//.</li>



<li>Confirm hook file path is correct and marked executable if it’s a script.</li>
</ul>
</li>



<li>Binary fails on host:
<ul class="wp-block-list">
<li>Ensure correct platform binary published or require dotnet runtime and use wrapper scripts to run dotnet MyFeatureRunner.dll.</li>



<li>Check file permissions: chmod +x on Unix binaries.</li>
</ul>
</li>



<li>Placeholder expansion:
<ul class="wp-block-list">
<li>Verify your Spec Kit installation supports placeholders like {{spec.path}}; consult your Spec Kit version docs and update hook args.</li>
</ul>
</li>



<li>Inspect logs:
<ul class="wp-block-list">
<li>Check Spec Kit or agent logs; hooks often have stdout/stderr captured — look for error messages and exit codes.</li>
</ul>
</li>



<li>Test locally:
<ul class="wp-block-list">
<li>Run the wrapper or the CLI directly with the same args the hook would send to reproduce failures.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Security &amp; governance checklist</p>



<ul class="wp-block-list">
<li>Do not embed secrets inside the extension. Use environment variables or configuration files that the consumer supplies.</li>



<li>Document any network calls or telemetry the extension performs.</li>



<li>Prefer offline, deterministic tools; minimize external dependencies.</li>



<li>Provide checksums and consider signing releases for consumers to verify integrity.</li>



<li>Encourage consumers to review source before installing; keep the extension small and source-available.</li>
</ul>



<p class="wp-block-paragraph">Best practices &amp; gotchas</p>



<ul class="wp-block-list">
<li>Use JSON for structured output; it’s easier for agents and hooks to parse than free text.</li>



<li>Keep the CLI deterministic and non-interactive. Hooks execute noninteractively — no prompts.</li>



<li>If you can’t ship native executables for all platforms, ship a single DLL and small launcher scripts that call dotnet MyRunner.dll; document runtime requirements.</li>



<li>Consider a self-test command or endpoint (e.g., /speckit.extn.myfeature.selftest) that the user can run after install to verify everything works.</li>
</ul>



<p class="wp-block-paragraph"><strong>Complete end-to-end checklist (before publishing)</strong></p>



<ul class="wp-block-list">
<li>extension.yml exists and maps files correctly.</li>



<li>addon/commands contains command files with examples and output schema.</li>



<li>addon/hooks reference run wrappers and include correct placeholders.</li>



<li>addon/bin contains published artifacts for supported OSes or a DLL + run wrappers.</li>



<li>README documents requirements, install, usage, and troubleshooting.</li>



<li>install scripts work for Unix and Windows for local testing.</li>



<li>Example spec exists and tests validate analyzer logic.</li>



<li>CI builds and packages artifacts and uploads ZIP to release/host.</li>



<li>Catalog entry (if publishing) includes correct URL and checksum.</li>
</ul>



<p class="wp-block-paragraph">Note: The initial draft of this post (and all the code) was written by <a href="https://github.com/JesseLiberty/BlogWriter/blob/main/README.md">BlogWriter</a> and then<br />edited by Jesse Liberty. Illustrations by Copilot. <strong>Caution</strong>: LLMs make mistakes; this post is offered as is.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Creating Spec Kit Presets Step-By-Step</title>
		<link>https://jesseliberty.com/2026/10/09/creating-spec-kit-presets-step-by-step/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 09 Oct 2026 12:33:06 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13851</guid>

					<description><![CDATA[This guide assumes you already understand what Spec Kit and presets are and focuses on the who/what/where/how of authoring, testing, and publishing a preset. The instructions are concrete and practical — including a minimal manifest example, the recommended folder layout, &#8230; <a href="https://jesseliberty.com/2026/10/09/creating-spec-kit-presets-step-by-step/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This guide assumes you already understand what Spec Kit and presets are and focuses on the who/what/where/how of authoring, testing, and publishing a preset. The instructions are concrete and practical — including a minimal manifest example, the recommended folder layout, local test workflow, a tiny C# validation snippet, and packaging/publishing notes.</p>



<figure class="wp-block-image size-large is-resized"><img decoding="async" width="800" height="796" src="https://jesseliberty.com/wp-content/uploads/2026/10/preset-wizrd-2-800x796.jpg" alt="" class="wp-image-13853" style="aspect-ratio:1.0050158209305051;width:400px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/preset-wizrd-2-800x796.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/10/preset-wizrd-2-150x149.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/10/preset-wizrd-2-300x298.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/10/preset-wizrd-2-768x764.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/10/preset-wizrd-2.jpg 971w" sizes="(max-width: 800px) 100vw, 800px" /></figure>



<span id="more-13851"></span>



<ul class="wp-block-list">
<li>A preset is a versioned package that supplies template files, CLI prompts/companion prompt text, small scripts, and tiny content changes (terminology, defaults, localization). Presets are layered at runtime; when Spec Kit resolves a template or prompt, it walks the stack, and the highest-priority “winning” file is used.</li>



<li>Presets are intended for content/style/localization changes. Avoid heavy runtime logic—use Bundles or Extensions if you need stateful automation.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step </strong>1—design: decide scope, naming, and conventions</p>



<ul class="wp-block-list">
<li>Pick exactly what you will override:
<ul class="wp-block-list">
<li>templates/—spec, plan, or task templates (Markdown).</li>



<li><strong>commands</strong>/—CLI command prompt text or companion .prompt.md files visible to users.</li>



<li>scripts/—shell/PowerShell scripts used by templates or lifecycle hooks.</li>



<li><strong>small localized wording changes, default values, or doc snippets.</strong></li>
</ul>
</li>



<li>Keep presets focused. <em>One preset should do one job</em> (branding, Spanish localization, trimmed plan structure, etc.).</li>



<li>Choose an ID, version, and a default priority. Example: id = com.example.my-company-style, version = 0.1.0, priority = 8. Lower priority number = higher precedence (default is 10).</li>



<li>Record author name/email and a short description in the manifest. These fields help consumers and catalogs list and audit presets.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step </strong>2—create the folder structure and the manifest</p>



<ul class="wp-block-list">
<li>Create a folder for the preset. Example:<br />my-company-style/</li>



<li>At the root, create a manifest file named preset.yml and add subfolders for overrides:
<ul class="wp-block-list">
<li>templates/</li>



<li>commands/</li>



<li>scripts/</li>



<li>README.md</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Minimal example</strong> preset.yml (place at my-company-style/preset.yml)<br />name: my-company-style<br />id: com.example.my-company-style<br />version: 0.1.0<br />description: &#8220;Company wording, trimmed plan structure, and Spanish localization.&#8221;<br />author:<br />name: Example Corp<br />email: <a href="mailto:infra@example.com">infra@example.com</a><br />priority: 8<br />tags:</p>



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



<li>localization</li>
</ul>



<p class="wp-block-paragraph"><strong>Notes about the manifest</strong>:</p>



<ul class="wp-block-list">
<li>Spec Kit reads preset.yml to register the preset.</li>



<li>The important required fields are id, name, and version; add description, author, priority, and tags for usability.</li>



<li>You can extend the manifest with catalog metadata if you plan to publish.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 3</strong>—place overrides in the correct locations</p>



<ul class="wp-block-list">
<li>templates/: copy the exact core template filename you want to override (for example, plan-template.md) and edit it. The runtime resolution uses filenames/paths to match.</li>



<li>commands/: put files that replace command prompt text or companion prompts. For example, speckit.constitution.md or .prompt.md files.</li>



<li>scripts/: put helper scripts referenced by templates or lifecycle hooks.</li>



<li>README.md: explain what the preset does, installation implications, and any migration guidance.</li>
</ul>



<p class="wp-block-paragraph"><strong>Important</strong>: keep the same filenames and relative paths used by the core Spec Kit repository (you can inspect core templates in the Spec Kit repo to find correct names). During installation, preset files are copied to .specify/presets// and spec resolution walks the stack automatically, so you don’t manually merge templates.</p>



<p class="wp-block-paragraph"><strong>Example</strong>: override the plan template to add a C# code sample</p>



<ul class="wp-block-list">
<li>Create templates/plan-template.md and include a fenced C# block in the example section: &#8230;in the template&#8217;s code example area include: <code>// Example using our company logging convention public class AccountService { private readonly ILogger _log; public AccountService(ILogger log) =&gt; _log = log; }</code></li>
</ul>



<p class="wp-block-paragraph">(Templates are plain Markdown — using C# samples inside them is fine.)</p>



<p class="wp-block-paragraph"><strong>Step 4</strong> — local testing during development (fast feedback loop)</p>



<ul class="wp-block-list">
<li>Install your preset into a local project while developing:<br />specify preset add &#8211;dev /path/to/my-company-style</li>



<li>Run generation commands that exercise the templates or prompts you modified:<br />specify plan &#8211;input spec=&#8221;Add user login&#8221;</li>



<li>Inspect the generated output to verify your overrides took effect.</li>



<li>Use Spec Kit helper commands to debug resolution:<br />specify preset resolve<br />specify artifact (or whichever command your Spec Kit provides to inspect which file “wins”)</li>



<li>Iterate: change files in your local preset directory and re-run the generation command. The &#8211;dev install mode points to your local copy for quick iteration.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step </strong>5—validate the manifest (quick programmatic check)</p>



<ul class="wp-block-list">
<li>A simple validation ensures required fields are present and helps catch YAML typos. Below is a tiny C# example using YamlDotNet that reads preset.yml and errors if id/name/version are missing.</li>
</ul>



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



<ul class="wp-block-list">
<li>Add the YamlDotNet package (run in your dotnet project directory):<br />dotnet add package YamlDotNet</li>
</ul>



<p class="wp-block-paragraph"><strong>C# Program</strong> (Program.cs):</p>



<pre class="wp-block-code"><code>using System;
using System.IO;
using System.Collections.Generic;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

var yaml = File.ReadAllText("preset.yml");
var deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
var preset = deserializer.Deserialize&lt;Dictionary&lt;string, object&gt;&gt;(yaml);

string&#91;] required = new&#91;] { "id", "name", "version" };
foreach (var r in required)
{
  if (!preset.ContainsKey(r))
  {
    Console.Error.WriteLine($"Missing required manifest field: {r}");
    Environment.Exit(2);
  }
}
Console.WriteLine($"Preset OK: {preset&#91;"name"]} v{preset&#91;"version"]}");</code></pre>



<ul class="wp-block-list">
<li>dotnet run (or dotnet build + dotnet run) in the project containing Program.cs and preset.yml.</li>



<li>Exit codes: 0 for OK, non-zero for missing required fields. Integrate this into CI to fail fast on manifest mistakes.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step </strong>6—tests and CI</p>



<ul class="wp-block-list">
<li>Unit or integration tests: create tests that run specify commands in a controlled workspace (temp directory), generate artifacts, and assert expected strings or structural pieces exist.</li>



<li>Snapshot testing: store small canonical generated files in tests and compare outputs after generation.</li>



<li>Linting: run your YAML validator and any custom checks (e.g., disallow certain words, ensure semantic versioning).</li>



<li>CI: run the validation and generation tests for pull requests. If your preset includes scripts, run them in CI to ensure cross-platform behavior or provide both shell and PowerShell variants.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step </strong>7—package and publish</p>



<ul class="wp-block-list">
<li>Options for sharing:
<ul class="wp-block-list">
<li>Publish the preset repository on GitHub/GitLab and reference it from a catalog.</li>



<li>Host a catalog.json or catalog.yaml listing your preset metadata and URL. Consumers can add presets from catalogs.</li>



<li>Use community tooling (e.g., presetify) to generate catalog entries and help with validation/publishing to community catalogs.</li>
</ul>
</li>



<li>For internal distribution, your team can host the preset repo or a catalog on an internal server and point projects at it.</li>



<li>Ensure you tag releases using semantic versioning so consumers can pin versions: e.g., v0.1.0.</li>
</ul>



<p class="wp-block-paragraph"><strong>Step 8</strong>—installation and lifecycle for consumers</p>



<ul class="wp-block-list">
<li>Install from a catalog or URL:<br />specify preset add<br />specify preset add &#8211;version<br />specify preset add &#8211;from</li>



<li>Install locally for development:<br />specify preset add &#8211;dev &lt;path/to/preset&gt;</li>



<li>Remove:<br />specify preset remove</li>



<li>Upgrading a preset is a matter of updating the version and having consumers run an upgrade or specify preset add with a version.</li>
</ul>



<p class="wp-block-paragraph"><strong>Best practices &amp; warnings</strong></p>



<ul class="wp-block-list">
<li>Keep presets limited to content/formatting/localization. Don’t use presets to implement complex runtime automation.</li>



<li>Follow semantic versioning. Document changes in README and changelog.</li>



<li>Prefer small, focused presets over large monolithic presets; it’s easier to compose and upgrade.</li>



<li>Security: don’t blindly trust third-party catalogs. Inspect preset contents before install and ensure your project policy permits external presets.</li>



<li>Avoid breaking template API contracts. If you change placeholders or required variables in a template, document the change and bump major version.</li>



<li>Use meaningful tags in preset.yml so consumers (and catalog tools) can filter presets: tags: [style, spanish, onboarding]</li>



<li>Provide README.md with examples: “How this preset changes plan-template.md” and sample generated output.</li>



<li>If you want to supply per-command prompts, include companion .prompt.md files under commands/ with clear naming.</li>
</ul>



<p class="wp-block-paragraph">Note: The initial draft of this post was written by <a href="https://github.com/JesseLiberty/BlogWriter/blob/main/README.md">BlogWriter</a> and then<br />edited by Jesse Liberty.  Illustrations by Copilot. <strong>Caution</strong>: LLMs make mistakes; this post is offered as is.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>4 Solutions to Spec Kit Issues</title>
		<link>https://jesseliberty.com/2026/10/08/4-solutions-to-spec-kit-issues/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Thu, 08 Oct 2026 19:33:32 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13847</guid>

					<description><![CDATA[Your design docs look different across teams. Some repos forget the QA gate. Integrations (Jira, CI) are half-baked and duplicated as bash scripts. Onboarding a new engineer requires five manual steps to wire the same set of templates, commands, and &#8230; <a href="https://jesseliberty.com/2026/10/08/4-solutions-to-spec-kit-issues/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Your design docs look different across teams. Some repos forget the QA gate. Integrations (Jira, CI) are half-baked and duplicated as bash scripts. Onboarding a new engineer requires five manual steps to wire the same set of templates, commands, and pipelines.</p>



<p class="wp-block-paragraph">Spec Kit exposes four primitives that solve these problems in a structured, maintainable way:</p>



<ul class="wp-block-list">
<li>Presets fix content and wording (make docs consistent).</li>



<li>Extensions add behavior and integrations (run code, call Jira/CI).</li>



<li>Workflows orchestrate multi-step, resumable pipelines (add gates and state).</li>



<li>Bundles package and version a curated set of the above (one-command onboarding).</li>
</ul>



<figure class="wp-block-image size-large is-resized"><img decoding="async" width="800" height="797" src="https://jesseliberty.com/wp-content/uploads/2026/10/image-800x797.png" alt="" class="wp-image-13848" style="aspect-ratio:1.0037689030385317;width:401px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/image-800x797.png 800w, https://jesseliberty.com/wp-content/uploads/2026/10/image-150x150.png 150w, https://jesseliberty.com/wp-content/uploads/2026/10/image-300x299.png 300w, https://jesseliberty.com/wp-content/uploads/2026/10/image-767x765.png 767w, https://jesseliberty.com/wp-content/uploads/2026/10/image.png 930w" sizes="(max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">This post explains each primitive plainly, shows concrete manifests and examples (CLI + Visual Studio Code), and gives authoring, troubleshooting, and operational guidance so you can pick the right tool for the job.</p>



<span id="more-13847"></span>



<p class="wp-block-paragraph"><strong>At-a-glance summary</strong></p>



<ul class="wp-block-list">
<li><strong>Preset </strong>— content &amp; prompt overrides (templates/command text). Use to change wording, templates, or organization-wide conventions without changing core code.</li>



<li><strong>Extension </strong>— new capabilities (commands, integrations, hooks). Use to add Jira, git automation, quality gates, or domain-specific behaviors.</li>



<li><strong>Workflow</strong> — a resumable, multi-step YAML pipeline that orchestrates Spec Kit commands, shell steps and human gates.</li>



<li><strong>Bundle </strong>— a versioned, shareable package that installs a curated set of presets, extensions, workflows, and steps together for a team/role.</li>
</ul>



<p class="wp-block-paragraph"><strong>What they change and when to pick them</strong></p>



<ul class="wp-block-list">
<li>Need different wording, templates, or company style? → Preset.</li>



<li>Need new integrations, commands, or hooks that run code? → Extension.</li>



<li>Need to automate a repeatable multi-step process (with gates and state)? → Workflow.</li>



<li>Need to ship a set of the above to teammates as one installable artifact? → Bundle.</li>
</ul>



<p class="wp-block-paragraph"><strong>Deep dive: Presets</strong> — change what the agent writes<br />What a preset does</p>



<ul class="wp-block-list">
<li>Presets override templates and prompt text used by Spec Kit’s agent. They are focused on content: wording, section structure, localization, default fields and the text the CLI uses when creating specs/plans/tasks.</li>



<li>Presets do not (normally) run code, call external services, or change engine behavior.</li>
</ul>



<p class="wp-block-paragraph"><strong>When to use a preset</strong></p>



<ul class="wp-block-list">
<li>Enforce company style, legal or compliance wording.</li>



<li>Provide localized templates (Spanish, Japanese).</li>



<li>Change default structure (one-pager vs full SDD).</li>



<li>Adjust agent prompt wording for tone/conciseness.</li>
</ul>



<p class="wp-block-paragraph">Concrete preset manifest (example)<br />File path: .specify/presets/company-style.yaml</p>



<pre class="wp-block-code"><code>priority: 10
name: company-style
description: "Company legal &amp; style overrides; Spanish localization for Latin America teams"

templates:
  spec.summary: "Propósito corto (1–2 oraciones)"
  spec.description: |
    Descripción:
    - Objetivo: {{ inputs.goal }}
    - Stakeholders: {{ inputs.stakeholders | join(', ') }}
  spec.security: |
    Consideraciones de seguridad:
    - Autenticación: se requiere 2FA para nuevos endpoints
    - Datos sensibles: cifrado en tránsito y en reposo</code></pre>



<p class="wp-block-paragraph"><strong>How presets stack</strong></p>



<ul class="wp-block-list">
<li>Multiple presets can be active. They stack by numeric priority: lower number = higher precedence. A global company preset (priority 10) can be overridden by a repo-level preset with priority 5.</li>



<li>When a template ID is present in multiple presets, the lowest-numbered preset wins for that template only.</li>
</ul>



<p class="wp-block-paragraph"><strong>Authoring and publishing a preset — step-by-step</strong></p>



<ol class="wp-block-list">
<li>Create a local preset file in .specify/presets/ (YAML or JSON).</li>



<li>Test locally by running a generate command that would use the template. Example:
<ul class="wp-block-list">
<li>CLI: specify plan &#8211;input spec=&#8221;Add login&#8221;</li>



<li>Check the generated text for your new wording.</li>
</ul>
</li>



<li>If satisfied, publish the preset to your Spec Kit catalog (your org’s registry) so teams can add it with specify preset add <a href="mailto:company-style@1.0.0">company-style@1.0.0</a>.</li>



<li>When updates are needed, bump the manifest version and publish a new release. Consumers can upgrade with specify preset update company-style.</li>
</ol>



<p class="wp-block-paragraph"><strong>VS Code: how to install and test a preset</strong></p>



<ul class="wp-block-list">
<li>Terminal:
<ol class="wp-block-list">
<li>Open your workspace in VS Code.</li>



<li>Open integrated terminal (Ctrl+`).</li>



<li>Run: specify preset add company-style</li>
</ol>
</li>



<li>UI (Spec Kit Companion):
<ol class="wp-block-list">
<li>Open the Spec Kit sidebar.</li>



<li>Select Presets → Search → company-style → Install.</li>



<li>Use the “Preview” or “Generate” action in the sidebar to see the template applied.</li>
</ol>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Troubleshooting common preset issues</strong></p>



<ul class="wp-block-list">
<li>Preset not taking effect: check priorities (lower wins), ensure the preset ID and version installed, and confirm the template ID you modified matches the agent’s template ID (typos happen).</li>



<li>Conflicting overrides: list installed presets and remove/adjust the one you don’t want (specify preset list; specify preset remove ).</li>
</ul>



<p class="wp-block-paragraph"><strong>Deep dive: Extensions</strong> — add behavior and integrations<br />What an extension does</p>



<ul class="wp-block-list">
<li>Extensions are plugins that can register new CLI commands, provide templates, offer hooks, and run code. They can call external services (Jira, test runners), enforce checks, or modify repository state (branch creation, commits).</li>



<li>Use extensions when you need logic beyond textual overrides.</li>
</ul>



<p class="wp-block-paragraph">Example extension manifest (minimal)<br />File: extension.yaml</p>



<pre class="wp-block-code"><code>id: git-jira
version: 0.1.0
name: "Git + Jira integration"
description: "Creates branches, creates Jira tickets, and wires plan acceptance to ticket creation."

commands:
  - id: git.create-branch
    command: specify git branch-for-spec
  - id: jira.create-issue
    command: specify jira create-issue --input summary="{{ inputs.summary }}"

hooks:
  onPlanAccepted: jira.create-issue

permissions:
  network:
    - "jira.example.com"
filesystem:
  read:
    - ".git"
  write:
    - ".git/refs/heads"</code></pre>



<p class="wp-block-paragraph"><strong>Authoring an extension — quick guide</strong></p>



<ol class="wp-block-list">
<li>Scaffold an extension (your preferred runtime: Node, shell scripts, or compiled binary). Keep it small and well-scoped.</li>



<li>Register commands in the manifest and map them to your executable or script.</li>



<li>Implement and mock external calls for local testing.</li>



<li>Test in a local repo:
<ul class="wp-block-list">
<li>Install locally: specify extension add ./path/to/extension</li>



<li>Run the new command: specify git create-branch &#8211;input spec=&#8221;&#8230;&#8221;</li>
</ul>
</li>



<li>Publish to catalog once reviewed.</li>
</ol>



<p class="wp-block-paragraph">Example behavior: create branch + Jira ticket when a plan is accepted</p>



<ul class="wp-block-list">
<li>Extension registers a hook onPlanAccepted.</li>



<li>When a workflow step marks the plan accepted, the extension is invoked to create a Jira ticket and a branch.</li>



<li>Because extensions can run code or call networks, follow security practices (see Security section).</li>
</ul>



<p class="wp-block-paragraph"><strong>VS Code: installing and using an extension</strong></p>



<ul class="wp-block-list">
<li>Terminal:
<ol class="wp-block-list">
<li>Open integrated terminal (Ctrl+`).</li>



<li>Run: specify extension add git-jira</li>
</ol>
</li>



<li>UI (Spec Kit Companion):
<ol class="wp-block-list">
<li>Presets/Extensions view → Search “git-jira” → Install.</li>



<li>The extension’s commands will show up in the Command Palette (Ctrl+Shift+P) under Spec Kit commands; or the companion UI exposes buttons like “Create Jira issue”.</li>
</ol>
</li>
</ul>



<p class="wp-block-paragraph">Troubleshooting extensions</p>



<ul class="wp-block-list">
<li>Network permissions denied: check the extension manifest’s network permission and your environment firewall.</li>



<li>Command missing after install: ensure the extension installed successfully and restart the terminal/VS Code if necessary.</li>
</ul>



<p class="wp-block-paragraph"><strong>Deep dive: Workflows </strong>— orchestrate multi-step, resumable pipelines<br />What a workflow does</p>



<ul class="wp-block-list">
<li>A workflow is a YAML pipeline that composes agent steps, shell steps, and human gates. Workflows persist run state so you can pause for review and resume later.</li>



<li>Use workflows to enforce reproducible processes: assess → plan → implement → test → sign-off.</li>
</ul>



<p class="wp-block-paragraph">Expanded example workflow (detailed)<br />File: sdd-pipeline.yml</p>



<pre class="wp-block-code"><code>name: sdd-pipeline
inputs:
  spec:
    type: string
    required: true

steps:
  - id: assess
    type: agent
    command: specify assess --input spec="{{ inputs.spec }}"
    outputs:
      summary: string

  - id: plan
    type: agent
    command: specify plan --input spec="{{ steps.assess.summary }}"
    outputs:
      branch: string
      plan_url: string

  - id: create-branch
    type: shell
    command: |
      git checkout -b "{{ steps.plan.branch }}"
      git push -u origin "{{ steps.plan.branch }}"
    continueOnError: false

  - id: run-tests
    type: shell
    command: |
      ./run-tests.sh --report results.json
    outputs:
      test_report: results.json
    continueOnError: false

  - id: review
    type: gate
    approvers:
      - "team-lead@example.com"
    timeout: "72h"</code></pre>



<p class="wp-block-paragraph"><strong>Key workflow features demonstrated</strong></p>



<ul class="wp-block-list">
<li>Inputs and typed outputs — later steps can reference earlier outputs (templating with {{ }}).</li>



<li>Mix of agent and shell steps.</li>



<li>Human gate with approvers and timeout.</li>



<li>Persisted run state to allow resumption.</li>
</ul>



<p class="wp-block-paragraph">Run workflows — CLI and VS Code</p>



<ul class="wp-block-list">
<li>Install catalog workflow:<br />specify workflow add sdd-pipeline</li>



<li>Run from catalog:<br />specify workflow run sdd-pipeline &#8211;input spec=&#8221;Add user authentication&#8221;</li>



<li>Run a local workflow YAML:<br />specify workflow run ./sdd-pipeline.yml &#8211;input spec=&#8221;Add payment flow&#8221;</li>
</ul>



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



<ul class="wp-block-list">
<li>Terminal: run the same specify workflow commands.</li>



<li>UI (Spec Kit Companion):
<ol class="wp-block-list">
<li>Open Workflows in the sidebar.</li>



<li>Choose a catalog workflow or point to a local YAML.</li>



<li>Click “Start run” and follow the run UI to see step outputs and approve gates.</li>
</ol>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Authoring a workflow</strong> — best practices</p>



<ul class="wp-block-list">
<li>Validate inputs: declare required inputs and types.</li>



<li>Make steps idempotent where possible (re-running a branch creation should be safe or fail cleanly).</li>



<li>Persist minimal but useful outputs for later steps (e.g., branch name, plan URL).</li>



<li>Add clear error handling: use continueOnError judiciously and provide logs for shell steps.</li>



<li>Test locally by running the workflow with small inputs and stepping through the UI or CLI.</li>
</ul>



<p class="wp-block-paragraph"><strong>Troubleshooting workflows</strong></p>



<ul class="wp-block-list">
<li>Workflow fails at shell step: open the run’s step logs, reproduce the commands locally in the terminal, fix failing command or environment.</li>



<li>Gate never approved: ensure approvers are reachable emails and the companion UI sends notifications; approvers can resume runs from VS Code or CLI.</li>
</ul>



<p class="wp-block-paragraph"><strong>Deep dive: Bundles</strong> — package and share a curated stack<br />What a bundle does</p>



<ul class="wp-block-list">
<li>A bundle is a versioned artifact that declares a set of presets, extensions, workflows, and steps to install together. Bundles don’t create new primitives — they package and pin existing ones so teams get a tested configuration with one command.</li>



<li>Bundles record provenance of what they installed for clean removal and upgrades.</li>
</ul>



<p class="wp-block-paragraph">Example bundle manifest<br />File: .specify/bundles/backend-team.yaml</p>



<pre class="wp-block-code"><code>id: backend-team
version: 1.0.0
name: "Backend Team Starter"
description: "Preset, extensions, and workflows for backend engineers."

presets:
  - company-style@10
  - backend-layout@5

extensions:
  - git@2.3.0
  - jira@1.1.0

workflows:
  - sdd-pipeline@1.0.0

metadata:
  installedBy: "bundle-maintainer@example.com"
  date: "2026-10-01"</code></pre>



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



<ul class="wp-block-list">
<li>Install a bundle:<br />specify bundle install backend-team<br />(alias: specify bundle add backend-team)</li>



<li>List installed bundles:<br />specify bundle list</li>



<li>Upgrade a bundle:<br />specify bundle update backend-team &#8211;version 1.1.0</li>



<li>Uninstall/remove a bundle:<br />specify bundle remove backend-team</li>
</ul>



<p class="wp-block-paragraph"><strong>What bundles actually do in a repo</strong></p>



<ul class="wp-block-list">
<li>Install referenced presets, extensions, and workflows into the project.</li>



<li>Create metadata in .specify/bundles/ to record installed components and pinned versions.</li>



<li>Allow a single remove command to uninstall everything the bundle introduced.</li>
</ul>



<p class="wp-block-paragraph"><strong>When to use a bundle</strong></p>



<ul class="wp-block-list">
<li>Onboarding new hires or new projects with a single command.</li>



<li>Ensuring consistent, pinned tooling across teams.</li>



<li>Distributing proven configurations for compliance or role-specific setups.</li>
</ul>



<p class="wp-block-paragraph">Bundles: VS Code experience</p>



<ul class="wp-block-list">
<li>Terminal:
<ol class="wp-block-list">
<li>Open integrated terminal (Ctrl+`).</li>



<li>Run: specify bundle install backend-team</li>
</ol>
</li>



<li>UI (Spec Kit Companion):
<ol class="wp-block-list">
<li>Bundles view → Search → backend-team → Install.</li>



<li>The UI shows progress and created files; it lists the installed components in a panel.</li>
</ol>
</li>
</ul>



<p class="wp-block-paragraph">Security, testing, and operational guidance<br />Vetting extensions</p>



<ul class="wp-block-list">
<li>Treat extensions like any plugin: require code review, run static analysis, and audit network permissions.</li>



<li>Use least-privilege: only grant network access to specific hosts used by the extension.</li>



<li>Use CI sandboxing: run untrusted extensions in CI jobs with restricted tokens and ephemeral credentials.</li>
</ul>



<p class="wp-block-paragraph"><strong>Testing workflows and presets</strong></p>



<ul class="wp-block-list">
<li>Presets: test by generating artifacts locally and comparing before/after outputs. Use a diff tool to confirm only intended changes.</li>



<li>Workflows: dry-run where possible. Validate inputs and run with small, safe inputs. Test gate behavior by simulating approvals.</li>



<li>Extensions: unit tests and integration tests against mocked APIs. Provide a local test harness script.</li>
</ul>



<p class="wp-block-paragraph"><strong>Bundles and provenance</strong></p>



<ul class="wp-block-list">
<li>Always pin versions in bundle manifests to avoid unexpected breaking changes.</li>



<li>Use bundle metadata to record who installed the bundle and when to enable audits and rollbacks.</li>
</ul>



<p class="wp-block-paragraph">Programmatic integration: invoking specify from C#<br />If you need to call the specify CLI from other tools or CI, capture stdout/stderr. Example (async):</p>



<pre class="wp-block-code"><code>using System.Diagnostics;

var psi = new ProcessStartInfo {
    FileName = "specify",
    Arguments = "workflow run sdd-pipeline --input spec=\"Build login\" --json",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true
};

using var proc = Process.Start(psi);
string stdout = await proc.StandardOutput.ReadToEndAsync();
string stderr = await proc.StandardError.ReadToEndAsync();
proc.WaitForExit();
// stdout contains run result (or --json output); stderr contains errors.</code></pre>



<p class="wp-block-paragraph">In VS Code, run this same program from the integrated terminal or incorporate it into a task that runs on save or during CI.</p>



<p class="wp-block-paragraph">VS Code practical steps — exact actions users will see</p>



<ul class="wp-block-list">
<li>Open Command Palette (Ctrl+Shift+P). Search “Spec Kit: Install Preset” or “Spec Kit: Run Workflow”.</li>



<li>Spec Kit Companion sidebar panels:
<ul class="wp-block-list">
<li>Presets: search, preview, install.</li>



<li>Extensions: list, view commands, install.</li>



<li>Workflows: catalog view, run, see run state and step logs.</li>



<li>Bundles: browse, install, view installed metadata.</li>
</ul>
</li>



<li>Terminals use standard CLI commands; the UI calls the same CLI under the hood.</li>
</ul>



<p class="wp-block-paragraph">Authoring tutorials (concise walkthroughs)<br />Preset authoring walkthrough (quick)</p>



<ol class="wp-block-list">
<li>Create file: .specify/presets/company-style.yaml with your overrides.</li>



<li>Test: specify plan &#8211;input spec=&#8221;Add login&#8221; and confirm the summary and security sections are replaced.</li>



<li>Publish to catalog and instruct teams: specify preset add <a href="mailto:company-style@1.0.0">company-style@1.0.0</a>.</li>
</ol>



<p class="wp-block-paragraph">Extension authoring walkthrough (quick)</p>



<ol class="wp-block-list">
<li>Scaffold a small Node or shell extension and manifest (extension.yaml).</li>



<li>Add a command that runs a script to call Jira’s API (use environment tokens).</li>



<li>Test by installing locally: specify extension add ./my-extension.</li>



<li>Publish after security review.</li>
</ol>



<p class="wp-block-paragraph">Workflow authoring walkthrough (quick)</p>



<ol class="wp-block-list">
<li>Start from sdd-pipeline.yml example above.</li>



<li>Run locally: specify workflow run ./sdd-pipeline.yml &#8211;input spec=&#8221;&#8230;&#8221;</li>



<li>Step through the run in VS Code and fix templating issues.</li>



<li>Publish and add to a bundle for distribution.</li>
</ol>



<p class="wp-block-paragraph">Bundle authoring walkthrough (quick)</p>



<ol class="wp-block-list">
<li>Create bundle manifest listing pinned components.</li>



<li>Run: specify bundle install ./backend-team.yaml locally to validate the install process.</li>



<li>Publish the bundle to the catalog. Team members then run specify bundle install backend-team.</li>
</ol>



<p class="wp-block-paragraph"><strong>Migration patterns and practical advice</strong></p>



<ul class="wp-block-list">
<li>Start with presets when you only need wording changes (low risk).</li>



<li>When you find repeating scripts or ad-hoc tools across repos, move them into an extension.</li>



<li>When a sequence of steps becomes common and should be reproducible, author a workflow.</li>



<li>When multiple teams need the same combination, wrap all pieces into a bundle.</li>
</ul>



<p class="wp-block-paragraph"><strong>FAQ / Troubleshooting quick hits</strong></p>



<ul class="wp-block-list">
<li>Preset not applied? Check priority: specify preset list shows installed presets and priority.</li>



<li>Workflow step failed — how to debug? View step logs in the CLI run output or VS Code run panel; reproduce the shell step locally.</li>



<li>How do I remove a bundle? specify bundle remove will uninstall components that bundle recorded.</li>



<li>An extension requires network access — is it safe? Review its permissions in the manifest and audit its code. Use least-privilege credentials.</li>
</ul>



<p class="wp-block-paragraph"><strong>Glossary</strong></p>



<ul class="wp-block-list">
<li>Preset: content and prompt overrides for templates and text.</li>



<li>Extension: plugin that adds commands, hooks, and runtime behavior.</li>



<li>Workflow: a persisted YAML pipeline combining steps, shell commands, and gates.</li>



<li>Bundle: a versioned package that installs presets, extensions, and workflows together.</li>
</ul>



<p class="wp-block-paragraph"><strong>Quick CLI reference</strong></p>



<ul class="wp-block-list">
<li>Add preset: specify preset add</li>



<li>Add extension: specify extension add</li>



<li>Add workflow from catalog: specify workflow add</li>



<li>Run workflow: specify workflow run &#8211;input spec=&#8221;&#8230;&#8221;</li>



<li>Run local workflow YAML: specify workflow run ./my-workflow.yml &#8211;input spec=&#8221;&#8230;&#8221;</li>



<li>Install bundle: specify bundle install</li>



<li>List bundles: specify bundle list</li>
</ul>



<p class="wp-block-paragraph">Closing and next steps<br />Presets, extensions, workflows, and bundles complement each other:</p>



<ul class="wp-block-list">
<li>Presets shape content and tone.</li>



<li>Extensions implement behavior and integrations.</li>



<li>Workflows orchestrate repeatable, resumable processes.</li>



<li>Bundles distribute curated stacks and keep provenance.</li>
</ul>



<p class="wp-block-paragraph">Note: The initial draft of this post was written by <a href="https://github.com/JesseLiberty/BlogWriter/blob/main/README.md">BlogWriter</a> and then<br />edited by Jesse Liberty.<br />Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Prompts in MCP Servers</title>
		<link>https://jesseliberty.com/2026/10/07/prompts-in-mcp-servers/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Wed, 07 Oct 2026 17:19:04 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13839</guid>

					<description><![CDATA[TL;DR Prompts allow you to manage the exact instructions your chat models receive and let apps pick those instructions at runtime. That&#8217;s the core value of “prompts” in the Model Context Protocol (MCP). MCP prompts let a server publish reusable, &#8230; <a href="https://jesseliberty.com/2026/10/07/prompts-in-mcp-servers/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">TL;DR</p>



<p class="wp-block-paragraph">Prompts allow you to manage the exact instructions your chat models receive and let apps pick those instructions at runtime.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="585" height="800" src="https://jesseliberty.com/wp-content/uploads/2026/10/prompts-585x800.jpg" alt="" class="wp-image-13843" style="aspect-ratio:0.7312447508740405;width:327px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/prompts-585x800.jpg 585w, https://jesseliberty.com/wp-content/uploads/2026/10/prompts-110x150.jpg 110w, https://jesseliberty.com/wp-content/uploads/2026/10/prompts-219x300.jpg 219w, https://jesseliberty.com/wp-content/uploads/2026/10/prompts-767x1049.jpg 767w, https://jesseliberty.com/wp-content/uploads/2026/10/prompts.jpg 932w" sizes="auto, (max-width: 585px) 100vw, 585px" /></figure>



<p class="wp-block-paragraph">That&#8217;s the core value of “prompts” in the Model Context Protocol (MCP). MCP prompts let a server publish reusable, argument‑driven prompt templates that clients (including Microsoft Agent Framework agents) can discover, retrieve, and use in LLM chat calls—so behavior, UX, and auditing are consistent across apps.</p>



<span id="more-13839"></span>



<p class="wp-block-paragraph">Key terms (quick definitions)</p>



<ul class="wp-block-list">
<li>MCP server: a service that implements the Model Context Protocol and exposes tools, prompts, and resources to clients.</li>



<li>Prompt template (MCP “prompt”): a server‑hosted, reusable instruction template that can accept named arguments and return one or more chat messages.</li>



<li>Prompt arguments: named values (e.g., url, language, timeframe) supplied by the client when calling prompts/get.</li>



<li>Prompt response messages: the chat messages (roles like system/user/assistant) returned by prompts/get that are ready to be appended to a model call.</li>



<li>Resource messages: special messages in a prompt response that contain URIs or attachments (files, repo links) the model should consider.</li>
</ul>



<p class="wp-block-paragraph">Why MCP prompts matter for developers and product teams</p>



<ul class="wp-block-list">
<li>Central control: product teams can update a prompt template on the server and clients will automatically get the new behavior the next time they fetch it.</li>



<li>Consistency: multiple agents and apps use the same instructions and checklists (e.g., compliance templates, code‑review checklists).</li>



<li>Reusability: prompts are parameterized. One server prompt can cover many scenarios by taking arguments.</li>



<li>Discoverability &amp; UX: prompts/list lets clients surface available commands (slash menus, command palettes) rather than hardcoding instructions into each client.</li>



<li>Auditability: when agents log which prompt and which arguments were used, teams can trace model behavior back to a server‑controlled template.</li>
</ul>



<p class="wp-block-paragraph">How prompts fit into the Microsoft Agent Framework<br />The Microsoft Agent Framework can connect to MCP servers at runtime. That allows an agent to:</p>



<ol class="wp-block-list">
<li>Call prompts/list to discover server prompts and show them to the user (for example, as a slash‑command menu).</li>



<li>Prompt the user for required arguments (URL, code snippet, language).</li>



<li>Call prompts/get (name + arguments) to fetch the populated template as chat messages.</li>



<li>Append the returned messages to the chat history and send them to the LLM client (e.g., Azure OpenAI).<br />Because the prompt is server‑hosted, the server controls wording, embedded resources, and any instruction to call tools—clients only provide arguments and deliver the resulting messages to the model.</li>
</ol>



<p class="wp-block-paragraph">Typical flow (concise)</p>



<ol class="wp-block-list">
<li>Discover: prompts/list → display options.</li>



<li>Select &amp; collect args: user picks a prompt → agent collects required fields.</li>



<li>Retrieve: prompts/get(name, args) → server returns promptContent.Messages and optionally resource messages.</li>



<li>Use: append returned messages to your chat request and send to the model.</li>
</ol>



<p class="wp-block-paragraph">Sample prompts/get JSON (small example)<br />This is a minimal illustration of what prompts/get might return.<br />{<br />&#8220;messages&#8221;: [<br />{ &#8220;role&#8221;: &#8220;system&#8221;, &#8220;content&#8221;: &#8220;You are an expert summarizer. Focus on key findings and risks.&#8221; },<br />{ &#8220;role&#8221;: &#8220;user&#8221;, &#8220;content&#8221;: &#8220;Summarize the document at: <a href="https://example.com/report.pdf">https://example.com/report.pdf</a>&#8221; }<br />],<br />&#8220;resources&#8221;: [<br />{ &#8220;type&#8221;: &#8220;uri&#8221;, &#8220;uri&#8221;: &#8220;<a href="https://example.com/report.pdf">https://example.com/report.pdf</a>&#8220;, &#8220;description&#8221;: &#8220;Source PDF&#8221; }<br />]<br />}</p>



<p class="wp-block-paragraph">C# examples (illustrative, practical, with error handling)<br />Notes: the exact MCP SDK namespace and method names vary by SDK version. The examples below follow common patterns used in the MCP quickstart and show how to map MCP messages into a chat client call. Replace transport/creation code with the actual constructors in your MCP SDK.</p>



<p class="wp-block-paragraph">Install (example)</p>



<ul class="wp-block-list">
<li>Add an MCP client package (example package id: ModelContextProtocol.Client)</li>



<li>Add your LLM client package (example: Azure.AI.OpenAI)</li>
</ul>



<p class="wp-block-paragraph">Example 1 — Summarize document (argument: url)<br />This example lists prompts, finds summarize-document, calls get with a URL, and sends returned messages to an Azure OpenAI chat client.</p>



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



<pre class="wp-block-preformatted">using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Threading;<br />using System.Threading.Tasks;<br />using ModelContextProtocol.Client; // example package<br />using Azure.AI.OpenAI;<br />using Azure;<br /><br />async Task SummarizeDocumentAsync(string documentUrl, CancellationToken cancellationToken = default)<br />{<br />try<br />{<br />// 1) Create MCP client (transport creation is SDK-specific)<br />var transport = /* create transport per your SDK: HTTP, WebSocket, etc. */;<br />var mcpClient = await McpClient.CreateAsync(transport, cancellationToken);<code> // 2) List prompts<br />    var prompts = await mcpClient.ListPromptsAsync(cancellationToken);<br />    var summarize = prompts.FirstOrDefault(p => p.Name == "summarize-document");<br />    if (summarize == null)<br />        throw new InvalidOperationException("Prompt 'summarize-document' not available.");<br /><br />    // 3) Call prompts/get with required arguments<br />    var args = new Dictionary&lt;string, object> { ["url"] = documentUrl };<br />    var promptContent = await summarize.GetAsync(args, cancellationToken);<br /><br />    // 4) Map MCP messages to OpenAI chat messages<br />    var openAiClient = new OpenAIClient(new Uri("https://your-azure-endpoint/"), new AzureKeyCredential("AZURE_KEY"));<br />    var chatOptions = new ChatCompletionsOptions();<br />    foreach (var m in promptContent.Messages)<br />    {<br />        chatOptions.Messages.Add(MapMcpMessageToChatMessage(m));<br />    }<br /><br />    // Optional: append follow-up instructions from the user<br />    chatOptions.Messages.Add(new ChatMessage(ChatRole.User, "Please keep the summary under 200 words."));<br /><br />    var response = await openAiClient.GetChatCompletionsAsync("deployment-name", chatOptions, cancellationToken);<br />    Console.WriteLine(response.Value.Choices[0].Message.Content);<br />}<br />catch (RequestFailedException ex) when (ex.Status == 404)<br />{<br />    Console.Error.WriteLine("Prompt not found: " + ex.Message);<br />}<br />catch (Exception ex)<br />{<br />    Console.Error.WriteLine("Error fetching prompt or calling model: " + ex.Message);<br />    throw;<br />}</code>}</pre>



<pre class="wp-block-code"><code>static ChatMessage MapMcpMessageToChatMessage(McpMessage m)
{
var role = m.Role switch
{
  system" => ChatRole.System,
  "assistant" => ChatRole.Assistant,
  _ => ChatRole.User
};
  return new ChatMessage(role, m.Content);
}</code></pre>



<p class="wp-block-paragraph">Example 2 — Code review (arguments: code, optional language)<br />This example demonstrates sending a code snippet and language argument to a code_review prompt. The server might return a checklist system message plus resource URIs pointing to related files.</p>



<pre class="wp-block-code"><code>async Task CodeReviewAsync(string codeSnippet, string language = "csharp", CancellationToken cancellationToken = default)
{
var transport = /* create transport */;
var mcpClient = await McpClient.CreateAsync(transport, cancellationToken);
var prompts = await mcpClient.ListPromptsAsync(cancellationToken);
var codePrompt = prompts.FirstOrDefault(p => p.Name == "code_review");
if (codePrompt == null) throw new InvalidOperationException("code_review not available");

var args = new Dictionary&lt;string, object> { &#91;"code"] = codeSnippet, &#91;"language"] = language };
var promptContent = await codePrompt.GetAsync(args, cancellationToken);

var openAiClient = new OpenAIClient(new Uri("https://your-azure-endpoint/"), new AzureKeyCredential("KEY"));
var chatOptions = new ChatCompletionsOptions();
foreach (var m in promptContent.Messages)
{
    chatOptions.Messages.Add(MapMcpMessageToChatMessage(m));
}

var response = await openAiClient.GetChatCompletionsAsync("deployment-name", chatOptions, cancellationToken);
Console.WriteLine(response.Value.Choices&#91;0].Message.Content);
}</code></pre>



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



<p class="wp-block-paragraph">Practical tips (concrete, actionable)</p>



<ul class="wp-block-list">
<li>Validate arguments on the client: whitelist allowed languages, enforce URL schemes (https), and check length limits. Refuse or sanitize inputs that look like secrets.</li>



<li>Prefer proxying or previewing third‑party resources instead of sending raw URIs to external servers. If you must share URIs, use signed, time‑limited links.</li>



<li>Handle common error codes: 404 = prompt not found, 400 = missing/invalid args, 429 = rate limit; implement exponential backoff and circuit breakers.</li>



<li>Cache prompt metadata: calls to prompts/list are relatively cheap but cache names/descriptions and refresh periodically to avoid UI delays.</li>



<li>UI recommendations: present prompts in a command palette or slash‑menu; show required arguments and a small template preview before fetching; allow an editable preview so users can tweak arguments before sending.</li>



<li>Embedded resources: if the prompt returns resource messages, decide whether to fetch the resource text (and include it in the chat) or attach a link. If fetching, enforce size limits and sanitize content.</li>



<li>Versioning &amp; breaking changes: because prompts are server‑controlled, add an ability to pin a prompt version (if the server supports it) or keep integration tests that validate prompt behavior after server updates.</li>



<li>Audit and tracing: log prompt name + non‑sensitive args and add trace IDs/headers when calling MCP servers so you can correlate user actions and prompt outputs for debugging and compliance.</li>
</ul>



<p class="wp-block-paragraph">Security &amp; operational specifics</p>



<ul class="wp-block-list">
<li>Redact secrets: never send API keys, passwords, or PII to a third‑party MCP server. Run argument sanitization and redaction on the client.</li>



<li>Signed URLs: use expiring signed URLs when providing attachments to external servers; this limits exposure.</li>



<li>Policy &amp; approval: treat server‑hosted prompts as part of your product’s compliance surface. Establish a governance process for who can change server prompts.</li>



<li>Headers &amp; audit: follow the Agent Framework guidance for adding audit headers and tracing to MCP calls. Store logs of prompt usage (prompt name, timestamp, actor) with care for privacy.</li>
</ul>



<p class="wp-block-paragraph">Error handling &amp; retries (short patterns)</p>



<ul class="wp-block-list">
<li>Use CancellationToken for timeouts.</li>



<li>Retry transient errors (HTTP 5xx, 429) with an exponential backoff and jitter.</li>



<li>Surface user‑friendly messages for 400/422 responses (missing args or invalid input) and guide the user to correct inputs.</li>
</ul>



<p class="wp-block-paragraph">Where to read next</p>



<ul class="wp-block-list">
<li>Model Context Protocol prompts spec: modelcontextprotocol.io (prompts/list and prompts/get shapes).</li>



<li>Microsoft Agent Framework docs: on integrating MCP prompts and security guidance.</li>



<li>.NET quickstart and MCP C# SDK samples: for precise types and SDK usage in production.</li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>When Is the LLM Called?</title>
		<link>https://jesseliberty.com/2026/10/06/when-is-the-llm-called/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 19:07:56 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13835</guid>

					<description><![CDATA[When working with agents (e.g., in Microsoft Agent Framework) it is easy to be confused as to when the LLM is called. Since calling the LLM (and getting responses) costs money (in the form of expended tokens) it is important &#8230; <a href="https://jesseliberty.com/2026/10/06/when-is-the-llm-called/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">When working with agents (e.g., in Microsoft Agent Framework) it is easy to be confused as to when the LLM is called. Since calling the LLM (and getting responses) costs money (in the form of expended tokens) it is important to understand this relationship.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="799" src="https://jesseliberty.com/wp-content/uploads/2026/10/slots-800x799.jpg" alt="" class="wp-image-13836" style="aspect-ratio:1.001237565964601;width:410px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/slots-800x799.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/10/slots-150x150.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/10/slots-300x300.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/10/slots-768x767.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/10/slots.jpg 965w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">An agent in the Microsoft Agent Framework (MAF) talks to an LLM whenever it needs to: <br />(1) decide what to do or generate text (initial planning/response generation), <br />(2) decide whether to call a tool and provide its arguments (function/tool calling), and <br />(3) incorporate tool outputs and continue reasoning or synthesize the final reply. </p>



<p class="wp-block-paragraph">A single agent run commonly produces multiple model calls in a loop: planning → tool call(s) → synthesis → (repeat if needed).</p>



<span id="more-13835"></span>



<p class="wp-block-paragraph">MAF implements a layered agent pipeline. Calling agent.RunAsync (or equivalent) starts a run that flows down middleware and context providers before ever hitting a model client (the LLM), and the run’s responses flow back up. That orchestration is why an agent doesn’t “just call the LLM once” by default—the framework determines when and how many model requests are required based on the agent’s instructions, tools, and middleware.</p>



<p class="wp-block-paragraph"><strong>Three middleware layers you can hook in</strong></p>



<ul class="wp-block-list">
<li>Agent-run middleware: wraps the entire run (fires once per run). Good for run-level telemetry or cancellation.</li>



<li>Function/tool middleware: wraps each tool invocation (fires for each tool call). Useful for gating tool execution, approvals, or stubbing tools for tests.</li>



<li>Chat / IChatClient middleware: wraps every underlying model/chat client call (fires every time the framework calls the model). Useful for logging prompts, redaction, or cost measurement.</li>
</ul>



<p class="wp-block-paragraph"><strong>Typical sequence that triggers LLM calls</strong></p>



<ol class="wp-block-list">
<li>Request received → pipeline and context providers populate run context (no model call yet).</li>



<li>Initial reasoning/plan → LLM call to decide plan (generate response or choose a tool). This is typically the first chat call.</li>



<li>If the model chooses a tool → framework executes function middleware and runs the tool (tool execution itself may be non-LLM code).</li>



<li>After tool returns → agent calls LLM again to integrate tool output and produce/synthesize the final response.</li>



<li>If needed, the LLM may ask to call another tool and the loop continues. Each cycle can add one (or more, depending on provider semantics) model calls.</li>
</ol>



<p class="wp-block-paragraph"><strong>C# examples</strong></p>



<ol class="wp-block-list">
<li>Minimal agent run (RunAsync typically triggers at least one LLM call)<br />This follows the quick-start pattern; RunAsync goes through the pipeline and will call the model as needed.</li>
</ol>



<pre class="wp-block-code"><code>using System;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;

var agent = new AIProjectClient(
new Uri("https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"),
new AzureCliCredential()
).AsAIAgent(
model: "gpt-5.4-mini",
instructions: "You are a helpful assistant. Keep answers brief."
);

var result = await agent.RunAsync("What is the largest city in France?");
Console.WriteLine(result);</code></pre>



<ol start="2" class="wp-block-list">
<li>Tool/function call flow (model decides to call a tool → tool executes → model synthesizes)<br />Sketch of registering a tool; the run will typically perform model → tool → model:</li>
</ol>



<pre class="wp-block-code"><code>// sketch
var myTool = new FunctionTool(
name: "get-weather",
description: "Get weather for a city",
handler: async (args) => {
return await GetWeatherAsync(args); // actual non-LLM work
}
);

var agent = new ChatClientAgentBuilder()
.UseModel("gpt-5.x")
.RegisterTool(myTool)
.Build();

// This RunAsync will likely:
// 1) call model to decide plan (may return "call get-weather")
// 2) execute myTool (no LLM)
// 3) call model to synthesize the final reply
var response = await agent.RunAsync("What's the weather in Seattle tomorrow?");
Console.WriteLine(response);</code></pre>



<ol start="3" class="wp-block-list">
<li>Chat middleware: intercept every LLM call (log each model invocation)<br />Chat-level middleware runs on every model/chat client request:</li>
</ol>



<pre class="wp-block-code"><code>async Task LoggingChatMiddleware(
IEnumerable messages,
ChatOptions? options,
IChatClient innerClient,
CancellationToken cancellationToken)
{
  Console.WriteLine($"&#91;ChatMiddleware] Sending {messages.Count()} messages to model...");
  var resp = await innerClient.GetResponseAsync(messages, options, cancellationToken);
  Console.WriteLine($"&#91;ChatMiddleware] Model returned {resp.Messages.Count} messages.");
  return resp;
}

// Register this middleware when configuring IChatClient / ChatClientAgent (see docs for exact APIs).</code></pre>



<ol start="4" class="wp-block-list">
<li>Agent-run and function middleware (brief sketches)</li>
</ol>



<ul class="wp-block-list">
<li>Agent-run middleware: wrap whole run for telemetry or per-run constraints.</li>



<li>Function middleware: intercept each tool invocation (e.g., require approval):</li>
</ul>



<pre class="wp-block-code"><code>// Function middleware sketch
async Task ApproveThenExecuteMiddleware(
ToolInvocation invocation,
Func&lt;ToolInvocation, Task> next,
CancellationToken ct)
{
  if (!IsApproved(invocation)) throw new Exception("Tool call not approved.");
    return await next(invocation); // proceed to actual tool handler
}</code></pre>



<p class="wp-block-paragraph">Practical caveats and best practices</p>



<ul class="wp-block-list">
<li><em>Multiple LLM calls per run are normal</em>. Instrument chat middleware to measure API usage and cost.</li>



<li>Tool execution is non-LLM work; function middleware can gate, mock, or log tool runs.</li>



<li>Providers differ: function-calling semantics, streaming behavior, and limits can change how often or how responses are streamed.</li>



<li>Use concise instructions and tool schemas so the model makes fewer unnecessary tool calls.</li>



<li>If you need strict accounting of model usage, intercept at chat middleware (every model call passes there).</li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>1,500 posts</title>
		<link>https://jesseliberty.com/2026/10/06/1500-posts/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 15:30:32 +0000</pubDate>
				<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13833</guid>

					<description><![CDATA[So far, this blog has published 1,500 posts.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">So far, this blog has published 1,500 posts.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>MCP and Microsoft Agent Framework</title>
		<link>https://jesseliberty.com/2026/10/06/mcp-and-microsoft-agent-framework/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 06 Oct 2026 15:21:16 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13828</guid>

					<description><![CDATA[tl;dr This post explains what MCP is, how servers and clients work, and how to connect MCP tools into the Microsoft Agent Framework with short, practical C# examples (stdio client, converting tools to AIFunctions, and a minimal ASP.NET Core MCP &#8230; <a href="https://jesseliberty.com/2026/10/06/mcp-and-microsoft-agent-framework/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">tl;dr</p>



<ul class="wp-block-list">
<li><strong>MCP </strong>(Model Context Protocol) is an open protocol that standardizes how AI hosts (models/ or agents) discover and call external “tools” and contextual data sources. Instead of building bespoke integrations for every service, an AI host can discover tools exposed by MCP servers and invoke them using a single, consistent API (tool list + function invocation/streaming).</li>



<li><strong>MCP server</strong>: a service that publishes tools (callable functions, data endpoints, long-running operations) via MCP over supported transports (stdio for local child processes, HTTP + SSE for remote/streamable). Servers are the canonical place to register capabilities agents can discover. *<em>SSE is an HTTP‑based streaming mechanism where the server keeps an HTTP connection open and continuously pushes events to the client</em></li>



<li><strong>MCP client</strong>: the client-side runtime your agent uses to discover and call tools exposed by one or more MCP servers. The MCP C# SDK provides transports and helpers to produce AIFunctions that integrate with Microsoft’s Agent Framework function-calling model.<br /><br /></li>
</ul>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="591" src="https://jesseliberty.com/wp-content/uploads/2026/10/toolbox-800x591.jpg" alt="" class="wp-image-13829" style="aspect-ratio:1.35363197272469;width:395px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/toolbox-800x591.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/10/toolbox-150x111.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/10/toolbox-300x222.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/10/toolbox-768x567.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/10/toolbox.jpg 1411w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<span id="more-13828"></span>



<p class="wp-block-paragraph">This post explains what MCP is, how servers and clients work, and how to connect MCP tools into the Microsoft Agent Framework with short, practical C# examples (stdio client, converting tools to AIFunctions, and a minimal ASP.NET Core MCP server). It assumes you already know the Microsoft Agent Framework and are familiar with adding AIFunctions to an agent.</p>



<p class="wp-block-paragraph">Why MCP matters for Agent Framework users</p>



<ul class="wp-block-list">
<li>Decoupling: your agent doesn’t need every skill embedded. Publish skills centrally on MCP servers and let agents discover them at runtime.</li>



<li>Reuse &amp; governance: maintain a canonical set of tools, centralize auth, logging, quotas, and policy.</li>



<li>Transport flexibility: local tools as child processes (stdio) during development, HTTP + SSE for remote production services with streaming and sessions.</li>



<li>Function-calling integration: the MCP C# SDK exposes helpers that translate MCP tools to AIFunction instances you can pass directly to the Agent Framework. That means the model’s function-calling flow can choose and invoke remote MCP tools as if they were local functions.</li>
</ul>



<p class="wp-block-paragraph"><strong>Core concepts</strong></p>



<ul class="wp-block-list">
<li>Tool: a callable capability. Each tool includes metadata (name, description, JSON schema for arguments, and optional streaming semantics). LLMs use this metadata to select and call appropriate tools.</li>



<li>Transport: how a client talks to a server. Common transports: stdio (spawned child), HTTP + SSE (remote streaming).</li>



<li>Session &amp; long-running tasks: MCP supports sessions and resumable/streamable operations so agents can work with long-running processes.</li>



<li>Security: MCP servers are APIs—protect them with OAuth, validate scopes/claims, and apply standard API hardening.</li>
</ul>



<p class="wp-block-paragraph"><strong>C# examples — preface and dependencies</strong></p>



<ul class="wp-block-list">
<li>The examples use the Model Context Protocol C# SDK and the Agent Framework integration helpers. Replace placeholders (endpoints, credentials, model clients) with your configuration.</li>



<li>NuGet packages you’ll typically use (check exact package names/versions for your environment): ModelContextProtocol.Client, ModelContextProtocol.AspNetCore (server-side), Microsoft.Extensions.AI (AIFunction types), and your Agent Framework package (the one you already use). The exact package names and APIs may evolve; consult the MCP C# SDK docs and Agent Framework docs for up-to-date guidance.</li>
</ul>



<p class="wp-block-paragraph">Example 1 — Minimal MCP client (stdio) that lists tools<br />Purpose: quickly launch a local MCP server as a child process and list the tools it exposes. Useful for local dev or verifying a server.</p>



<p class="wp-block-paragraph">C# (minimal)</p>



<pre class="wp-block-code"><code>using System;
using System.Threading.Tasks;
using ModelContextProtocol.Client;
using ModelContextProtocol.Client.Transports;

class Program
{
    static async Task Main()
    {
        // Start a local MCP server as a child process. Adjust command/arguments.
        var transport = new StdioClientTransport(new StdioClientTransportOptions
        {
            Command = "dotnet",
            Arguments = new&#91;] { "run", "--project", "../MyMcpServer" },
            Name = "LocalMcp"
        });

        using var mcpClient = await McpClient.CreateAsync(transport);

        var tools = await mcpClient.ListToolsAsync();
        Console.WriteLine("Tools available from MCP server:");
        foreach (var t in tools)
        {
            Console.WriteLine($"- {t.Name}: {t.Description}");
        }
    }
}</code></pre>



<p class="wp-block-paragraph">What this does</p>



<ul class="wp-block-list">
<li>It spawns your server project (via dotnet run) and uses the stdio transport to speak MCP to it.</li>



<li>ListToolsAsync fetches the catalog of published tools (names, descriptions, and parameter schemas).</li>
</ul>



<p class="wp-block-paragraph"><strong>Notes</strong></p>



<ul class="wp-block-list">
<li>Use stdio for development, local tooling, or launching untrusted tools in a sandboxed child process. For production remote servers use HTTP + SSE.</li>



<li>Always handle cancellation tokens and process lifecycle (timeouts, crashes) robustly in production code.</li>
</ul>



<p class="wp-block-paragraph">Example 2 — Convert MCP tools to AIFunctions and add them to an Agent<br />Purpose: let your agent use discovered remote tools via the Agent Framework’s function-calling mechanism (the model sees tools as functions it can call).</p>



<p class="wp-block-paragraph">C# (sketch)</p>



<pre class="wp-block-code"><code>using System;
using System.Threading;
using System.Threading.Tasks;
using ModelContextProtocol.Client;
using ModelContextProtocol.Client.Transports;
using Microsoft.Extensions.AI;                // AIFunction types
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Agents.AI;                    // your Agent Framework types

class Program
{
    static async Task Main()
    {
        // 1) Create an MCP client (stdio example)
        var transport = new StdioClientTransport(new StdioClientTransportOptions
        {
            Command = "dotnet",
            Arguments = new&#91;] { "run", "--project", "../MyMcpServer" }
        });

        using var mcpClient = await McpClient.CreateAsync(transport);

        // 2) Convert discovered tools to AIFunctions using the SDK helper
        // The SDK exposes an extension/method that returns IEnumerable&lt;AIFunction&gt;
        var aiFunctions = await mcpClient.GetAIFunctionsAsync(cancellationToken: CancellationToken.None);
        // aiFunctions are ready to pass into the Agent Framework.

        // 3) Build your ChatClient (your existing model client). Pseudocode below.
        IChatClient chatClient = BuildYourChatClientWithFunctionInvocation(); // use OpenAI/AzureOpenAI etc.

        // 4) Create an agent with the discovered functions available to the model
        var agent = chatClient.AsAIAgent(instructions: "You may call MCP tools as needed.", tools: aiFunctions);

        var result = await agent.RunAsync("Check invoice 12345 and return the payment date.");
        Console.WriteLine(result.Text);
    }

    static IChatClient BuildYourChatClientWithFunctionInvocation()
    {
        // Build your model client with function-calling enabled. This is highly dependent
        // on which model SDK you use; use the Agent Framework pattern you already have.
        throw new NotImplementedException();
    }
}</code></pre>



<p class="wp-block-paragraph">Key points and gotchas</p>



<ul class="wp-block-list">
<li>The MCP SDK returns AIFunction-compatible objects (or adapters) for MCP tools; you can pass them straight into the agent constructor/options used by the Agent Framework. No manual JSON schema translation usually required.</li>



<li>Pay attention to parameter schemas: MCP tool parameter schemas are JSON Schema-based. The AIFunction adapter will present these to the model, but ensure you validate arguments server-side as well.</li>



<li>Streaming/long-running tools: if a tool supports streaming or sessions, the adapter may offer streaming bindings or expose session tokens that the Agent Framework can handle. Check the SDK docs for streaming APIs and how they map to your chat client’s streaming model.</li>
</ul>



<p class="wp-block-paragraph">Example 3 — Minimal ASP.NET Core MCP server (HTTP + SSE)<br />Purpose: expose tools over HTTP + SSE so remote agents can discover and invoke them. This is a minimal Program.cs sketch; register and implement your tools separately.</p>



<p class="wp-block-paragraph">Program.cs</p>



<pre class="wp-block-code"><code>using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.AspNetCore; // server integration

var builder = WebApplication.CreateBuilder(args);

// Add MCP server services (package provides extension methods)
builder.Services.AddModelContextProtocol();

var app = builder.Build();

// Map the MCP endpoints. This registers the endpoints the MCP HTTP transport expects (e.g. /sse, /messages).
app.MapMcp(); // extension method from ModelContextProtocol.AspNetCore

app.Run();</code></pre>



<p class="wp-block-paragraph">Registering tools</p>



<ul class="wp-block-list">
<li>The SDK provides APIs to register tools (stateless tools, session-aware tools, or long-running handlers). Implement each tool as a class or function and register it with the MCP server service registration API (see SDK docs for registration patterns). Tools are described with name, description, and input schema.</li>
</ul>



<p class="wp-block-paragraph"><strong>Security</strong></p>



<ul class="wp-block-list">
<li>Protect MCP endpoints with OAuth 2.1 / bearer tokens (Microsoft Entra ID recommended). Validate scopes and claims before allowing tool invocation. Don’t expose privileged tools without strict auth checks.</li>



<li>Apply standard API hardening: CORS restrictions, host filtering, rate limiting, input validation, logging/auditing, and parameter sanitization. Treat tool execution as code execution—apply sandboxing, denylisting, and runtime quotas where appropriate.</li>
</ul>



<p class="wp-block-paragraph">Example: minimal tool registration sketch (pseudocode)</p>



<pre class="wp-block-code"><code>// Pseudocode - consult the SDK for exact API names
builder.Services.AddModelContextProtocol()
    .AddTool&lt;MyTool&gt;("CheckInvoice", options =&gt; { /* metadata, schema */ });</code></pre>



<p class="wp-block-paragraph">Practical notes when connecting MCP and Agent Framework</p>



<ol class="wp-block-list">
<li>Discovery vs. shipping tools
<ul class="wp-block-list">
<li>You can ship a few “core” tools inside the agent and discover more via MCP at startup or dynamically during a session. Consider caching the tool catalog to avoid repeated network calls.</li>
</ul>
</li>



<li>Function invocation flow
<ul class="wp-block-list">
<li>The Agent Framework’s function-calling flow is typically: model chooses a function, agent runtime serializes arguments according to the function schema, calls the function, and returns results to the model. MCP adapters wire that call to an MCP server invocation. Handle timeouts and partial results for long-running operations.</li>
</ul>
</li>



<li>Streaming &amp; sessions
<ul class="wp-block-list">
<li>For streaming outputs (SSE), ensure your chat client and agent can accept streamed partial results or forward them as incremental responses to the model. For session-based tools (long-running jobs), the server will usually hand back a session ID or event stream you can poll or subscribe to. Map session lifecycle into your agent’s state model.</li>
</ul>
</li>



<li>Error handling &amp; observability
<ul class="wp-block-list">
<li>Log tool discovery, invocation arguments, responses, and errors. Capture correlation IDs. Consider returning sanitized error messages to the model while logging full details internally.</li>
</ul>
</li>



<li>Security &amp; governance checklist
<ul class="wp-block-list">
<li>Require OAuth bearer tokens and validate tokens on every request.</li>



<li>Use scoped permissions: separate discovery (tool list) vs. execution scopes.</li>



<li>Limit which apps/users can call elevated tools.</li>



<li>Rate-limit and quota tools to reduce abuse.</li>



<li>Validate input against declared JSON schema server-side (never trust client input).</li>



<li>Sanitize logs to avoid leaking sensitive data.</li>



<li>Audit tool invocations with user identity and request context.</li>



<li>Consider a per-tool approval workflow for sensitive operations (human-in-the-loop).</li>
</ul>
</li>
</ol>



<p class="wp-block-paragraph">Debugging tips</p>



<ul class="wp-block-list">
<li>If ListToolsAsync returns nothing: check transport connectivity, server process logs (stdio), firewall/CORS (HTTP), and version compatibility between client/server SDKs.</li>



<li>For JSON schema mismatches: ensure server-published tool schemas are valid JSON Schema and that the client SDK version understands the schema features used.</li>



<li>For streaming issues: check that the server pushes SSE frames and that the client transport is configured to read/forward events incrementally.</li>



<li>For auth issues: inspect token audience (resource indicator), scopes, and clock skew. Entra ID configuration examples are in Microsoft docs.</li>
</ul>



<p class="wp-block-paragraph">Real-world use cases (short)</p>



<ul class="wp-block-list">
<li>Integrating Teams / Copilot: expose an MCP server that can send messages, create approvals, or read group context—agents discover these tools to orchestrate workflows.</li>



<li>Enterprise services: expose CRM, ERP, or Dynamics 365 actions via MCP so Copilot/agents can act on domain data without embedding connectors in every agent.</li>



<li>IDE tooling: expose project or infra operations (build, deploy, ticket lookup) as MCP tools so in-editor agents can perform actions safely and auditable.</li>
</ul>



<p class="wp-block-paragraph"><strong>Where to go from here</strong></p>



<ul class="wp-block-list">
<li>Official docs and samples:
<ul class="wp-block-list">
<li>Model Context Protocol (overview): <strong>Microsoft Learn</strong> MCP overview.</li>



<li>MCP C# SDK repo and samples: modelcontextprotocol/csharp-sdk on GitHub.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Appendix — runnable minimal project tips and csproj hints</p>



<ul class="wp-block-list">
<li>Minimal dependencies you’ll likely need (adjust exact package IDs to match the SDK versions you pick):
<ul class="wp-block-list">
<li>ModelContextProtocol.Client</li>



<li>ModelContextProtocol.AspNetCore (server)</li>



<li>Microsoft.Extensions.AI</li>



<li>Your Agent Framework package (the one you already use)</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Example csproj snippet (replace versions and package names with exact ones you choose)</p>



<pre class="wp-block-code"><code>&lt;Project Sdk="Microsoft.NET.Sdk"&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;
    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
    &lt;Nullable&gt;enable&lt;/Nullable&gt;
  &lt;/PropertyGroup&gt;

  &lt;ItemGroup&gt;
    &lt;PackageReference Include="ModelContextProtocol.Client" Version="x.y.z" /&gt;
    &lt;PackageReference Include="ModelContextProtocol.AspNetCore" Version="x.y.z" /&gt;
    &lt;PackageReference Include="Microsoft.Extensions.AI" Version="x.y.z" /&gt;
    &lt;!-- Add your Agent Framework package here --&gt;
  &lt;/ItemGroup&gt;
&lt;/Project&gt;</code></pre>



<p class="wp-block-paragraph">Note: The initial draft of this post was written by <a href="https://github.com/JesseLiberty/BlogWriter/blob/main/README.md">BlogWriter</a> and then <br />edited by Jesse Liberty.<br />Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>RAG in Microsoft Agent Framework Step By Step</title>
		<link>https://jesseliberty.com/2026/10/01/rag-in-microsoft-agent-framework-step-by-step/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Thu, 01 Oct 2026 14:55:39 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13820</guid>

					<description><![CDATA[Adding RAG to Microsoft Agent Framework is both straightforward and confusing. OK, that makes no sense, but I&#8217;ve been struggling to understand all the steps. Here&#8217;s a first approximation, more to come in future posts (and I have a video &#8230; <a href="https://jesseliberty.com/2026/10/01/rag-in-microsoft-agent-framework-step-by-step/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Adding RAG to Microsoft Agent Framework is both straightforward and confusing. OK, that makes no sense, but I&#8217;ve been struggling to understand all the steps. Here&#8217;s a first approximation, more to come in future posts (and I have a video coming with Bruno Capuano and Jon Galloway on this very subject). There are also a bunch of RAG blog posts that I&#8217;ve already posted; you can find the list <a href="https://jesseliberty.com">here</a>.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="784" src="https://jesseliberty.com/wp-content/uploads/2026/10/index-finger-800x784.jpg" alt="" class="wp-image-13821" style="aspect-ratio:1.0203940792917996;width:351px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/10/index-finger-800x784.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/10/index-finger-150x147.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/10/index-finger-300x294.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/10/index-finger-768x753.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/10/index-finger.jpg 929w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph"><br /><br />TL;DR</p>



<ul class="wp-block-list">
<li>Learn the simple C# steps to wire Retrieval-Augmented Generation (RAG) into Microsoft Agent Framework (MAF).</li>



<li>Includes a runnable, self-contained console stub that chunks documents, stores them in-memory with metadata, runs a demo similarity search, and returns answers plus source citations.</li>



<li>Swap the demo token-overlap search for real embeddings + a vector DB in production and then pass the search delegate into MAF’s TextSearchProvider.</li>
</ul>



<span id="more-13820"></span>



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



<ol class="wp-block-list">
<li>Chunk documents and record metadata (Id, SourceName, SourceLink).</li>



<li>Compute embeddings for each chunk and store (vector DB or in-memory for prototype).</li>



<li>Implement a search delegate: query → embedding → nearest neighbors → map to TextSearchResult-like results.</li>



<li>Attach the search delegate to MAF via TextSearchProvider. Choose between injecting passages (BeforeAIInvoke) or exposing search as a callable tool (OnDemandFunctionCalling).</li>



<li>Run the agent; show model response and the underlying source metadata for traceability.</li>
</ol>



<p class="wp-block-paragraph"><strong>Notes on packages and API names</strong></p>



<ul class="wp-block-list">
<li>The relevant MAF package is typically Microsoft.Agents.AI (check the exact package and namespace for your MAF version on NuGet and the MAF docs).</li>



<li>Typical using directives you might need:<br />using Microsoft.Agents.AI;<br />using Microsoft.Agents.AI.TextSearchProvider;</li>



<li>API names and enum names (e.g., TextSearchBehavior.BeforeAIInvoke) may vary by MAF version—verify your installed NuGet version and adjust namespaces accordingly.</li>
</ul>



<p class="wp-block-paragraph"><strong>Quick actionable steps</strong> </p>



<ol class="wp-block-list">
<li>Prepare documents</li>
</ol>



<ul class="wp-block-list">
<li>Split long content into chunks (500–1000 chars recommended) with some overlap (50–100 chars) to preserve context.</li>



<li>For each chunk, store:
<ul class="wp-block-list">
<li>Id (unique)</li>



<li>SourceName (file name)</li>



<li>SourceLink (URL)</li>



<li>Text (chunk text)</li>



<li>Optional metadata (section title, date)</li>
</ul>
</li>



<li>Compute an embedding for each chunk (production) and store the embedding vector alongside metadata.</li>
</ul>



<ol start="2" class="wp-block-list">
<li>Implement the search delegate</li>
</ol>



<ul class="wp-block-list">
<li>Signature MAF expects (canonical shape you should adapt to your MAF version):<br />Func&lt;string, CancellationToken, Task&lt;IEnumerable&lt;TextSearchProvider.TextSearchResult>>></li>



<li>Implementation steps:
<ul class="wp-block-list">
<li>Convert query → embedding (same embedding model used for docs).</li>



<li>Query vector DB for top-K neighbors (approx. nearest neighbor).</li>



<li>Map each hit to TextSearchResult (Text, SourceName, SourceLink, RawRepresentation).</li>
</ul>
</li>
</ul>



<ol start="3" class="wp-block-list">
<li>Create and configure a TextSearchProvider</li>
</ol>



<ul class="wp-block-list">
<li>Instantiate a TextSearchProvider with your search delegate and options (behavior, top_k).</li>



<li>Add to ChatClientAgentOptions.AIContextProviders so the agent can use it.</li>
</ul>



<ol start="4" class="wp-block-list">
<li>Run the agent and surface metadata</li>
</ol>



<ul class="wp-block-list">
<li>Decide the mode:
<ul class="wp-block-list">
<li>BeforeAIInvoke—passages injected into prompt before model call (simpler, higher token usage).</li>



<li>OnDemandFunctionCalling—model invokes search explicitly (more control/cost saving).</li>
</ul>
</li>



<li>Return the model response, and surface SourceName/SourceLink/RawRepresentation to the user for verification.</li>
</ul>



<p class="wp-block-paragraph"><strong>Scoring and thresholds</strong> </p>



<ul class="wp-block-list">
<li>Use cosine similarity on embeddings—normalized between -1 and 1. Typical positive ranges near 0.7–0.95 indicate high similarity depending on your model and vector dims.</li>



<li>Prototype token-overlap scores are uncalibrated. Use a minScore cutoff (e.g., 0.05–0.2) to filter noise and avoid returning unrelated passages.</li>



<li>Top-K: usually 3–5 passages works well to balance context and token cost.</li>
</ul>



<p class="wp-block-paragraph"><strong>Troubleshooting quick tips</strong></p>



<ul class="wp-block-list">
<li>If queries return nothing: check chunking (too small chunks can lose context) and tokenization differences (embedding model vs. tokenization used in your search).</li>



<li>If irrelevant results: increase overlap, reduce chunk size, or switch to a stronger embedding model.</li>



<li>If costs are high: use OnDemandFunctionCalling and caching of query embeddings.</li>
</ul>



<p class="wp-block-paragraph">Runnable, self-contained C# console stub</p>



<ul class="wp-block-list">
<li>Purpose: demonstrate chunking, adding documents with metadata, a demo token-overlap similarity search, returning top-K hits, and showing source metadata.</li>



<li>This demo is intentionally self-contained and uses a simple token-overlap scoring (NOT production quality). Replace the demo search and Tokenize with real embeddings + vector DB.</li>
</ul>



<p class="wp-block-paragraph">Create a new console app:<br />dotnet new console -n SimpleRagDemo<br />Replace Program.cs with the code below and run dotnet run.</p>



<p class="wp-block-paragraph"><strong>Code (runnable stub)</strong></p>



<pre class="wp-block-code"><code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Text;

// Simple Document chunk container with metadata
record DocumentChunk(string Id, string SourceName, string SourceLink, string Text);

// Result shape similar to TextSearchProvider.TextSearchResult
record SearchResult(string Text, string SourceName, string SourceLink, double Score, string RawRepresentation);

class SimpleRagStore
{
    private readonly List&lt;DocumentChunk&gt; _chunks = new();

    // Add a chunk (in production you'd also persist an embedding vector here)
    public void AddChunk(DocumentChunk chunk) =&gt; _chunks.Add(chunk);

    // Demo-only: token-overlap similarity (replace with embeddings + vector DB in production)
    // Returns topK search results (and ignores zero-score results by default)
    public Task&lt;IEnumerable&lt;SearchResult&gt;&gt; SearchAsync(string query, int topK = 3, double minScore = 0.0, CancellationToken ct = default)
    {
        ct.ThrowIfCancellationRequested();

        var qTokens = Tokenize(query);
        if (qTokens.Count == 0) return Task.FromResult(Enumerable.Empty&lt;SearchResult&gt;());

        var scored = _chunks.Select(c =&gt;
        {
            var dTokens = Tokenize(c.Text);
            var overlap = qTokens.Intersect(dTokens).Count();
            // Simple normalized score for demo (not cosine)
            var score = overlap == 0 ? 0.0 : overlap / Math.Sqrt(qTokens.Count * dTokens.Count);
            return new SearchResult(
                Text: c.Text,
                SourceName: c.SourceName,
                SourceLink: c.SourceLink,
                Score: score,
                RawRepresentation: $"chunk:{c.Id}"
            );
        })
        .Where(r =&gt; r.Score &gt; minScore)
        .OrderByDescending(r =&gt; r.Score)
        .Take(topK);

        return Task.FromResult(scored.AsEnumerable());
    }

    // Demo tokenization: extract word-like tokens, lower-case, remove short stop-words.
    // Production: keep consistent tokenization with embedding model or skip and use vector similarity.
    private static HashSet&lt;string&gt; Tokenize(string text)
    {
        var matches = Regex.Matches(text.ToLowerInvariant(), @"\w+"); // letters/digits/underscore
        var set = new HashSet&lt;string&gt;();
        foreach (Match m in matches)
        {
            var tok = m.Value;
            if (tok.Length &lt;= 2) continue; // skip tiny tokens in this demo
            set.Add(tok);
        }
        return set;
    }
}

static class ChunkingHelper
{
    // Split a long document into chunks with overlap.
    // chunkSize characters and overlap characters.
    public static IEnumerable&lt;string&gt; ChunkText(string text, int chunkSize = 800, int overlap = 100)
    {
        if (string.IsNullOrEmpty(text)) yield break;
        if (chunkSize &lt;= overlap) throw new ArgumentException("chunkSize must be greater than overlap");

        int start = 0;
        while (start &lt; text.Length)
        {
            int len = Math.Min(chunkSize, text.Length - start);
            yield return text.Substring(start, len);
            start += (chunkSize - overlap);
        }
    }
}

class Program
{
    // Placeholder: in production you'd call your embedding model here.
    // Keep this method so it's clear where to swap in real embeddings.
    static Task&lt;double&#91;]&gt; GetEmbeddingAsync(string text, CancellationToken ct = default)
    {
        // Demo does not use embeddings. Return a dummy vector for shape.
        return Task.FromResult(new double&#91;0]);
    }

    static async Task Main(string&#91;] args)
    {
        var store = new SimpleRagStore();

        // Example long document (will be chunked)
        var longDoc = @"This document explains how to reset your password. You can reset your password
by visiting /account/reset and following the instructions. Reset tokens last 1 hour.
If you don't receive an email, check your spam folder and contact support at support@example.com.";

        // 1) Chunk the long document and add chunks with metadata
        int chunkSize = 200; // small for demo visibility
        int overlap = 40;
        int chunkIndex = 0;
        foreach (var chunkText in ChunkingHelper.ChunkText(longDoc, chunkSize, overlap))
        {
            var id = $"pwd-doc-{chunkIndex++}";
            store.AddChunk(new DocumentChunk(
                Id: id,
                SourceName: "AccountFAQ.md",
                SourceLink: "https://example/docs/account-faq",
                Text: chunkText.Trim()
            ));
        }

        // Add a few short docs (already small)
        store.AddChunk(new DocumentChunk("install-1", "GettingStarted.md", "https://example/docs/getting-started",
            "To install the product, run dotnet tool install -g Example.Tool and then run example init."));
        store.AddChunk(new DocumentChunk("rn-1", "ReleaseNotes.md", "https://example/docs/releases",
            "Version 2.1 adds RAG support via a TextSearchProvider and includes an in-memory connector."));

        Console.WriteLine("Simple RAG demo. Ask a question about the docs (type 'exit' to quit).");

        while (true)
        {
            Console.Write("&gt; ");
            var question = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(question)) continue;
            if (question.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) break;

            // 2) Search (this mimics the search delegate you'd pass into TextSearchProvider)
            var results = await store.SearchAsync(question, topK: 3, minScore: 0.0, CancellationToken.None);

            // 3) Synthesize a simple grounded answer (demo-only)
            if (!results.Any())
            {
                Console.WriteLine("No grounded passages found. (In production you might still call your LLM but mark as 'no sources found')");
                continue;
            }

            var best = results.First();
            var outBuilder = new StringBuilder();
            outBuilder.AppendLine("Answer (grounded):");
            outBuilder.AppendLine($" - {best.Text}");
            outBuilder.AppendLine();
            outBuilder.AppendLine("Sources:");
            foreach (var r in results)
            {
                outBuilder.AppendLine($" - {r.SourceName} ({r.SourceLink})  &#91;score={r.Score:F3}] raw={r.RawRepresentation}");
            }

            Console.WriteLine(outBuilder.ToString());
        }
    }
}</code></pre>



<p class="wp-block-paragraph"><strong>How to swap the demo for real production components</strong></p>



<ul class="wp-block-list">
<li>Replace Tokenize + token-overlap similarity with:
<ul class="wp-block-list">
<li>Embeddings: call your embeddings model (Azure OpenAI or OpenAI), get float[] vectors for each chunk and for the query.</li>



<li>Vector store: persist chunk vectors + metadata to Qdrant, Redis, Azure Cognitive Search (vector-capable) or pgvector and query via nearest-neighbor search (cosine or dot-product).</li>



<li>Map DB hits to TextSearchProvider.TextSearchResult and return top-K.</li>
</ul>
</li>



<li>Ensure consistent embedding model between document and query vectors.</li>
</ul>



<p class="wp-block-paragraph">Mapping the search delegate into Microsoft Agent Framework (sketch)</p>



<ul class="wp-block-list">
<li>Search delegate signature (adapt to MAF version):<br />Func&lt;string, CancellationToken, Task&lt;IEnumerable&lt;TextSearchProvider.TextSearchResult>>></li>



<li>Example wiring (pseudo):</li>
</ul>



<pre class="wp-block-code"><code>// Adapt namespaces &amp; types for your MAF version
async Task&lt;IEnumerable&lt;TextSearchProvider.TextSearchResult&gt;&gt; MySearchAsync(string q, CancellationToken ct)
{
    var qVec = await MyEmbeddingClient.GetEmbeddingAsync(q, ct);
    var hits = await MyVectorDb.SearchAsync(qVec, topK: 4, ct);
    return hits.Select(h =&gt; new TextSearchProvider.TextSearchResult
    {
        Text = h.Text,
        SourceName = h.Metadata&#91;"sourceName"],
        SourceLink = h.Metadata.TryGetValue("link", out var l) ? l : null,
        RawRepresentation = h.Id
    });
}

var textSearchProvider = new TextSearchProvider(MySearchAsync, new TextSearchProviderOptions
{
    // Behavior = TextSearchBehavior.BeforeAIInvoke or OnDemandFunctionCalling
});
var agentOptions = new ChatClientAgentOptions
{
    Name = "MyRagAgent",
    AIContextProviders = new&#91;] { textSearchProvider }
};</code></pre>



<ul class="wp-block-list">
<li>Note: check MAF docs for exact type/namespace names for your NuGet version.</li>
</ul>



<p class="wp-block-paragraph">Practical tuning checklist</p>



<ul class="wp-block-list">
<li>Chunk size: 500–1000 chars is common. Use overlaps of 50–150 chars.</li>



<li>Top-K: start with 3–5. Increase if model still hallucinates.</li>



<li>Min score: filter out low similarity hits.</li>



<li>Mode: BeforeAIInvoke for reliability; OnDemandFunctionCalling for cost control.</li>
</ul>



<p class="wp-block-paragraph">Security and cost considerations</p>



<ul class="wp-block-list">
<li>Embeddings and model calls cost money—cache embeddings and reuse them.</li>



<li>Remove/obfuscate PII before storing embeddings if required by privacy rules.</li>



<li>Log and surface source metadata for auditability.</li>
</ul>



<p class="wp-block-paragraph">Sample console output (what to expect)</p>



<ul class="wp-block-list">
<li>User: &#8220;How do I reset my password?&#8221;</li>



<li>Output:<br />Answer (grounded):
<ul class="wp-block-list">
<li>This document explains how to reset your password. You can reset your password by visiting /account/reset and following the instructions.<br />Sources:</li>



<li>AccountFAQ.md (<a href="https://example/docs/account-faq">https://example/docs/account-faq</a>) [score=0.62] raw=chunk:pwd-doc-0</li>



<li>AccountFAQ.md (<a href="https://example/docs/account-faq">https://example/docs/account-faq</a>) [score=0.45] raw=chunk:pwd-doc-1</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Note: The initial draft of this post was written by <a href="https://github.com/JesseLiberty/BlogWriter/blob/main/README.md">BlogWriter</a> and then edited by Jesse Liberty.<br />Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Running Microsoft Agent Framework Locally</title>
		<link>https://jesseliberty.com/2026/09/27/running-microsoft-agent-framework-locally/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Sun, 27 Sep 2026 12:01:53 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13811</guid>

					<description><![CDATA[Run a full Microsoft Agent Framework (MAF) agent entirely on-device to eliminate round trips to the cloud, reduce latency, avoid egress costs, and keep sensitive data private. With Foundry Local you can run optimized local model variants (ONNX/WinML) and wire &#8230; <a href="https://jesseliberty.com/2026/09/27/running-microsoft-agent-framework-locally/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Run a full Microsoft Agent Framework (MAF) agent entirely <strong>on-device</strong> to eliminate round trips to the cloud, reduce latency, avoid egress costs, and keep sensitive data private. With Foundry Local you can run optimized local model variants (ONNX/WinML) and wire them directly into MAF. </p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="774" height="730" src="https://jesseliberty.com/wp-content/uploads/2026/09/Local-Foundry.jpg" alt="" class="wp-image-13812" style="aspect-ratio:1.0602591365412195;width:375px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/Local-Foundry.jpg 774w, https://jesseliberty.com/wp-content/uploads/2026/09/Local-Foundry-150x141.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/Local-Foundry-300x283.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/Local-Foundry-768x724.jpg 768w" sizes="auto, (max-width: 774px) 100vw, 774px" /></figure>



<p class="wp-block-paragraph">In this guide you’ll get a working quickstart (Windows .NET 7 + WinML and a cross-platform Python example), exact commands to install and download a model, copy‑and‑paste code, hardware/driver tips, observability and troubleshooting checks, CI/CD advice, and production‑ready recommendations.</p>



<span id="more-13811"></span>



<p class="wp-block-paragraph"><strong>What you’ll accomplish</strong></p>



<ul class="wp-block-list">
<li>Install Foundry Local (runtime + SDK) and pick/download a local model from the Foundry catalog.</li>



<li>Create a minimal MAF agent that uses Foundry Local as the model provider.</li>



<li>Run the agent locally in C# (.NET 7 on Windows using WinML) and in Python (cross-platform).</li>



<li>Diagnose common problems (OOM, slow inference, missing model) and tune model choices.</li>
</ul>



<p class="wp-block-paragraph"><strong>Quick overview (high-level steps)</strong></p>



<ol class="wp-block-list">
<li>Install Foundry Local SDK/CLI for your OS.</li>



<li>Use the Foundry CLI or SDK to list and download a model alias.</li>



<li>Add MAF + Foundry provider packages to your project (dotnet or pip).</li>



<li>Configure the client (FOUNDRY_LOCAL_MODEL env var or pass at construction).</li>



<li>Run the agent — Foundry Local will load the optimized model and MAF will call it for inference.</li>
</ol>



<p class="wp-block-paragraph"><strong>Prerequisites</strong></p>



<ul class="wp-block-list">
<li>OS: Windows, macOS, or Linux</li>



<li>.NET SDK: dotnet 7+ (if using C#)</li>



<li>Python: 3.8+ (if using Python)</li>



<li>Disk space: model downloads vary (see catalog); first download requires internet</li>



<li>Hardware: CPU-only works, but GPU/NPU provides much faster inference. Ensure GPU drivers &amp; runtimes are installed (see GPU section below).</li>



<li>Command-line familiarity, ability to set environment variables</li>
</ul>



<p class="wp-block-paragraph"><strong>Quickstart (10–15 commands)</strong><br />This is a condensed path from zero to a running agent (Windows example for .NET then Python). If you prefer a single OS, follow that section below in full.</p>



<p class="wp-block-paragraph"><em>Windows (.NET 7 + WinML quick path)</em></p>



<ol class="wp-block-list">
<li>Install dotnet 7 SDK (if not installed).</li>



<li>Create a new console app and add packages:
<ul class="wp-block-list">
<li>dotnet new console -n FoundryAgentDemo</li>



<li>cd FoundryAgentDemo</li>



<li>dotnet add package Microsoft.Agents.AI</li>



<li>dotnet add package Microsoft.Agents.AI.Foundry &#8211;prerelease</li>



<li>dotnet add package Microsoft.AI.Foundry.Local.WinML</li>
</ul>
</li>



<li>Set model alias (PowerShell):
<ul class="wp-block-list">
<li>$env:FOUNDRY_LOCAL_MODEL = &#8220;phi-4-mini&#8221;</li>
</ul>
</li>



<li>Build &amp; run:
<ul class="wp-block-list">
<li>dotnet build</li>



<li>dotnet run<br />(On first run Foundry Local will download the chosen model. Expect time for the initial download.)</li>
</ul>
</li>
</ol>



<p class="wp-block-paragraph"><em>Cross-platform Python quick path</em></p>



<ol class="wp-block-list">
<li>Create virtualenv and install (adjust package names if necessary — see details below):
<ul class="wp-block-list">
<li>python -m venv venv &amp;&amp; source venv/bin/activate</li>



<li>pip install agent-framework foundry-local-sdk</li>
</ul>
</li>



<li>Set model alias (bash):
<ul class="wp-block-list">
<li>export FOUNDRY_LOCAL_MODEL=&#8221;phi-4-mini&#8221;</li>
</ul>
</li>



<li>Run main.py (see Python example below)</li>
</ol>



<p class="wp-block-paragraph">Important note: package and CLI names evolve. If a command fails, consult the Foundry Local docs and the official sample repos (foundry-samples and agent-framework) which contain tested examples and exact package/namespace names.</p>



<p class="wp-block-paragraph"><strong>Part A — Install Foundry Local and download a model (details)</strong></p>



<ol class="wp-block-list">
<li>Install options<br />Foundry Local can be consumed via:</li>
</ol>



<ul class="wp-block-list">
<li>NuGet packages for .NET (recommended for C# apps)</li>



<li>PyPI packages (for Python)</li>



<li>npm packages (for JS)</li>



<li>Native installers or a platform-specific CLI where available</li>
</ul>



<p class="wp-block-paragraph">Where to get the right installer/packages</p>



<ul class="wp-block-list">
<li>Microsoft Learn Foundry Local get-started (follow the platform-specific instructions)</li>



<li>Official GitHub samples: microsoft-foundry/foundry-samples and microsoft/agent-framework</li>
</ul>



<ol start="2" class="wp-block-list">
<li>Example .NET installation (project-level)<br />You don’t usually “install” the runtime globally for .NET; add the Foundry Local package(s) to your project:</li>
</ol>



<ul class="wp-block-list">
<li>dotnet add package Microsoft.AI.Foundry.Local (Linux/macOS/CPU/GPU)</li>



<li>On Windows to use WinML/DirectML acceleration:
<ul class="wp-block-list">
<li>dotnet add package Microsoft.AI.Foundry.Local.WinML</li>
</ul>
</li>
</ul>



<ol start="3" class="wp-block-list">
<li>Example Python installation</li>
</ol>



<ul class="wp-block-list">
<li>pip install foundry-local-sdk</li>



<li>pip install agent-framework<br />(Exact pip package names may vary across releases; the sample repo shows exact names. If pip cannot find the package, clone/from-source in the foundry-samples repo.)</li>
</ul>



<ol start="4" class="wp-block-list">
<li>The Foundry CLI (catalog management)<br />Foundry exposes a catalog of local model aliases and (often) a CLI for listing/downloading models. CLI names vary by release (examples you may see in docs: foundryctl, foundry). Typical CLI flow:</li>
</ol>



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



<li>foundryctl catalog inspect phi-4-mini</li>



<li>foundryctl model download phi-4-mini &#8211;path /path/to/foundry/models</li>
</ul>



<p class="wp-block-paragraph">If your platform’s docs show a different CLI name, use that. The initial model download can be large; download location and cache are configurable in the runtime docs.</p>



<ol start="5" class="wp-block-list">
<li>Choosing a model</li>
</ol>



<ul class="wp-block-list">
<li>Check alias metadata for VRAM/disk estimates and license.</li>



<li>If you have limited VRAM, pick smaller or quantized variants.</li>



<li>Common small aliases: phi-4-mini, phi-2-mini, qwen-7b-quant (names change — check the catalog).</li>
</ul>



<p class="wp-block-paragraph"><strong>Part B — Copy‑paste ready C# example (Windows .NET 7 + WinML)</strong></p>



<p class="wp-block-paragraph">Important: package names and API surfaces can change between SDK releases. The commands below use package names commonly seen in the Microsoft samples. If NuGet packages change, run the dotnet add package commands without versions to get the latest packages, and refer to the foundry-samples repo for exact Program.cs if you encounter API differences.</p>



<ol class="wp-block-list">
<li>Project setup (commands)</li>
</ol>



<ul class="wp-block-list">
<li>dotnet new console -n FoundryAgentWinML</li>



<li>cd FoundryAgentWinML</li>



<li>dotnet add package Microsoft.Agents.AI</li>



<li>dotnet add package Microsoft.Agents.AI.Foundry &#8211;prerelease</li>



<li>dotnet add package Microsoft.AI.Foundry.Local.WinML</li>



<li>(optional) dotnet add package Microsoft.Extensions.Hosting</li>
</ul>



<ol start="2" class="wp-block-list">
<li>Example Program.cs (copy into Program.cs)<br />(This example shows the pattern: create Foundry local client, wrap in MAF chat agent, and run a prompt. Adjust namespaces if your SDK version uses slightly different names.)</li>
</ol>



<p class="wp-block-paragraph">using System;<br />using System.Threading.Tasks;<br />using Microsoft.Agents.AI;<br />using Microsoft.Agents.AI.Foundry;</p>



<p class="wp-block-paragraph">class Program<br />{<br />static async Task Main(string[] args)<br />{<br />// Read model alias from environment or use a default<br />var modelAlias = Environment.GetEnvironmentVariable(&#8220;FOUNDRY_LOCAL_MODEL&#8221;) ?? &#8220;phi-4-mini&#8221;;</p>



<pre class="wp-block-preformatted"> <code>   Console.WriteLine($"Using Foundry local model: {modelAlias}");<br /><br />    // Create a FoundryLocalClient (provider glue). Exact constructor may vary by SDK version.<br />    var foundryClient = new FoundryLocalClient(model: modelAlias);<br /><br />    // Create agent wrapper using a ChatClientAgent helper (example API)<br />    var agentOptions = new ChatClientAgentOptions<br />    {<br />        SystemPrompt = "You are a helpful assistant that summarizes text concisely."<br />    };<br /><br />    var agent = ChatClientAgent.FromChatClient(foundryClient, agentOptions);<br /><br />    var input = "Summarize the plan for the project in one sentence.";<br />    Console.WriteLine($"Sending prompt: {input}");<br /><br />    var result = await agent.RunAsync(input);<br />    Console.WriteLine("Agent response:");<br />    Console.WriteLine(result);<br />}<br /></code>}</pre>



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



<ol start="3" class="wp-block-list">
<li>Build and run</li>
</ol>



<ul class="wp-block-list">
<li>Open PowerShell and set the model alias:
<ul class="wp-block-list">
<li>$env:FOUNDRY_LOCAL_MODEL = &#8220;phi-4-mini&#8221;</li>
</ul>
</li>



<li>dotnet build</li>



<li>dotnet run</li>
</ul>



<p class="wp-block-paragraph"><strong>Notes</strong></p>



<ul class="wp-block-list">
<li>On first run, Foundry Local will download and prepare model artifacts. Check the console logs for model download progress.</li>



<li>If you want to pass the model explicitly instead of using an env var, call the FoundryLocalClient constructor with the desired alias (see SDK docs).</li>
</ul>



<p class="wp-block-paragraph"><strong>Part C — Copy‑paste ready Python example (cross-platform)</strong></p>



<ol class="wp-block-list">
<li>Create virtual environment and install (adjust if package names differ)</li>
</ol>



<ul class="wp-block-list">
<li>python -m venv venv</li>



<li>source venv/bin/activate (macOS/Linux)</li>



<li>venv\Scripts\Activate.ps1 (Windows PowerShell)</li>



<li>pip install agent-framework foundry-local-sdk</li>
</ul>



<p class="wp-block-paragraph">If PyPI names differ or packages aren’t present, clone the sample repo and install from source.</p>



<ol start="2" class="wp-block-list">
<li>requirements.txt (example)<br />agent-framework<br />foundry-local-sdk</li>



<li>main.py (copy into your project)<br />import os<br />import asyncio</li>
</ol>



<p class="wp-block-paragraph">from agent_framework import Agent<br />from agent_framework.foundry import FoundryLocalClient</p>



<p class="wp-block-paragraph">async def main():<br />model_alias = os.environ.get(&#8220;FOUNDRY_LOCAL_MODEL&#8221;, &#8220;phi-4-mini&#8221;)<br />print(f&#8221;Using Foundry local model: {model_alias}&#8221;)</p>



<pre class="wp-block-code"><code># Create Foundry client (constructor may vary)
client = FoundryLocalClient(model=model_alias)

# Create a simple agent wrapper — adjust arguments to match the library you installed
agent = Agent(chat_client=client, instructions="You are a brief assistant that summarizes text.")

response = await agent.run("Explain Docker in one sentence.")
print("Agent response:", response)</code></pre>



<p class="wp-block-paragraph">if <strong>name</strong> == &#8220;<strong>main</strong>&#8220;:<br />asyncio.run(main())</p>



<ol start="4" class="wp-block-list">
<li>Run</li>
</ol>



<ul class="wp-block-list">
<li>export FOUNDRY_LOCAL_MODEL=&#8221;phi-4-mini&#8221; (macOS/Linux)</li>



<li>$env:FOUNDRY_LOCAL_MODEL = &#8220;phi-4-mini&#8221; (Windows PowerShell)</li>



<li>python main.py</li>
</ul>



<p class="wp-block-paragraph"><strong>Notes</strong></p>



<ul class="wp-block-list">
<li>Use the sample repo’s Python examples to get exact import paths if the package you installed exposes different modules.</li>



<li>On first run the Foundry runtime will download the model. Monitor logs.</li>
</ul>



<p class="wp-block-paragraph"><strong>Part D — Configure Foundry Local without env var (programmatic control)</strong><br />Instead of environment variables, pass the model alias when you create the client:</p>



<ul class="wp-block-list">
<li>C#:<br />var foundryClient = new FoundryLocalClient(model: &#8220;qwen2.5-1.5b-instruct&#8221;);</li>



<li>Python:<br />client = FoundryLocalClient(model=&#8221;qwen2.5-1.5b-instruct&#8221;)</li>
</ul>



<p class="wp-block-paragraph">This is useful for runtime selection, multi-agent scenarios, or when you want config inside your app.</p>



<p class="wp-block-paragraph">Hardware, drivers and model sizing (practical guidance)</p>



<ul class="wp-block-list">
<li>NVIDIA GPUs: install NVIDIA drivers + CUDA toolkit compatible with the Foundry runtime. Many ONNX/WinML optimizations target CUDA 11.x; check Foundry docs for the exact CUDA/cuDNN versions required for a given release. Verify installation with:
<ul class="wp-block-list">
<li>nvidia-smi</li>
</ul>
</li>



<li>AMD GPUs: ROCm support varies by OS and GPU generation. Check ROCm compatibility guide.</li>



<li>Windows DirectML / WinML: WinML provides DirectML acceleration; keep Windows and GPU drivers up-to-date. For Intel GPUs, install the latest Graphics drivers.</li>



<li>VRAM and disk: model sizes and VRAM requirements are model-specific. Smaller models and quantized variants (4-bit/8-bit) are recommended for constrained devices. Use the Foundry catalog to inspect model metadata (size and suggested device types).</li>



<li>If GPU memory is insufficient, pick (a) a smaller model, (b) a quantized variant, or (c) fall back to CPU execution (slower).</li>
</ul>



<p class="wp-block-paragraph"><strong>What to look for in the Foundry catalog</strong></p>



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



<li>Disk size and estimated VRAM</li>



<li>Quantization variants offered (4-bit/8-bit)</li>



<li>License and usage restrictions</li>
</ul>



<p class="wp-block-paragraph"><strong>Observability and logging</strong></p>



<ul class="wp-block-list">
<li>Enable MAF OpenTelemetry integration to capture agent spans (system prompt, tool calls, model inference).</li>



<li>Foundry Local logs (model download, provider selection, errors) appear in the runtime output. If you start Foundry as a background service, logs typically go to console files or a configured log directory — check the Foundry docs for the exact log file location on your OS.</li>



<li>Example telemetry flow (conceptual):
<ul class="wp-block-list">
<li>Start tracing in your app (OpenTelemetry SDK).</li>



<li>Agent spans: pre-processing (prompt assembly), model inference (Foundry span), post-processing (tool calls).</li>



<li>Use traces to identify slow stages (model load vs. token-by-token decoding).</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Troubleshooting checklist (concrete remedies)</strong></p>



<ol class="wp-block-list">
<li>Model not found / invalid alias</li>
</ol>



<ul class="wp-block-list">
<li>Confirm alias with the Foundry catalog CLI:
<ul class="wp-block-list">
<li>foundryctl catalog list</li>



<li>foundryctl catalog inspect</li>
</ul>
</li>



<li>Ensure FOUNDRY_LOCAL_MODEL is set correctly.</li>
</ul>



<ol start="2" class="wp-block-list">
<li>First run stalls on download</li>
</ol>



<ul class="wp-block-list">
<li>Check network connectivity and disk space.</li>



<li>Watch Foundry log lines showing download progress.</li>



<li>Consider pre-downloading models on CI machines to avoid repeated downloads.</li>
</ul>



<ol start="3" class="wp-block-list">
<li>Slow inference</li>
</ol>



<ul class="wp-block-list">
<li>Confirm GPU provider selection: check Foundry logs for “selected execution provider” lines.</li>



<li>Ensure GPU drivers and runtimes are properly installed (nvidia-smi, rocminfo).</li>



<li>Use a smaller or quantized model if GPU not available.</li>
</ul>



<ol start="4" class="wp-block-list">
<li>Out-of-memory / VRAM errors</li>
</ol>



<ul class="wp-block-list">
<li>Use smaller model alias or quantized variant.</li>



<li>Use CPU execution for lower memory devices.</li>



<li>If running multiple models concurrently, reduce concurrency or run only one model at a time.</li>
</ul>



<ol start="5" class="wp-block-list">
<li>API/namespace mismatches</li>
</ol>



<ul class="wp-block-list">
<li>If compilation fails due to missing types/namespaces, check the sample repo for the exact package and API names for your SDK version. The foundry-samples and agent-framework samples are the definitive, copy‑paste-ready references.</li>
</ul>



<ol start="6" class="wp-block-list">
<li>Tooling and hosted features unavailable</li>
</ol>



<ul class="wp-block-list">
<li>Foundry Local is a local chat client. Some hosted Foundry tools (hosted web search, hosted code interpreter) are not available locally. Build local replacements or use Foundry hosted services when you need managed tools.</li>
</ul>



<p class="wp-block-paragraph">Forensics &amp; logs: where to look</p>



<ul class="wp-block-list">
<li>Application logs (your process) — model client creation and errors.</li>



<li>Foundry runtime logs — download progress, execution provider selection.</li>



<li>OS and GPU driver logs (nvidia-smi, dxdiag on Windows).</li>



<li>CI logs for model download step (cache model artifacts to speed repeated runs).</li>
</ul>



<p class="wp-block-paragraph">CI/CD and production considerations</p>



<ul class="wp-block-list">
<li>Avoid repeated large downloads in ephemeral CI runners by caching the model artifacts. Use a build cache or artifact store with pre-downloaded model files.</li>



<li>For smoke tests, use small local models or mocked chat clients to validate agent logic without heavy downloads.</li>



<li>Memory budgeting: if multiple agents or other services use the same GPU, coordinate startup to avoid OOM.</li>



<li>Hybrid deployment: run smaller on-device models for latency/privacy, and use hosted Foundry for heavy tooling or when you need managed, up-to-date models and scalable inference.</li>
</ul>



<p class="wp-block-paragraph">Security and licensing checklist</p>



<ul class="wp-block-list">
<li>Check model license in the Foundry catalog — confirm commercial usage terms if applicable.</li>



<li>Store no secrets in code. Foundry Local does not require cloud API keys for local inference, but MAF or other integrations might — use secure stores.</li>



<li>Limit permissions on model cache directories and follow OS best practices for process isolation.</li>
</ul>



<p class="wp-block-paragraph">Performance tuning tips</p>



<ul class="wp-block-list">
<li>Batch inference when possible (if the agent supports multiple concurrent requests).</li>



<li>Use quantized model variants for lower memory and faster CPU inference; be conscious of potential accuracy trade-offs.</li>



<li>Prefer GPU acceleration when available — it typically reduces latency per request significantly.</li>



<li>Warm-up the model after download by running a small inference to optimize runtime caches.</li>
</ul>



<p class="wp-block-paragraph"><strong>Appendix: Useful command snippets (summary)</strong></p>



<p class="wp-block-paragraph">Set env var</p>



<ul class="wp-block-list">
<li>Windows PowerShell:
<ul class="wp-block-list">
<li>$env:FOUNDRY_LOCAL_MODEL = &#8220;phi-4-mini&#8221;</li>
</ul>
</li>



<li>macOS / Linux:
<ul class="wp-block-list">
<li>export FOUNDRY_LOCAL_MODEL=&#8221;phi-4-mini&#8221;</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">Dotnet project (example)</p>



<ul class="wp-block-list">
<li>dotnet new console -n FoundryAgentDemo</li>



<li>dotnet add package Microsoft.Agents.AI</li>



<li>dotnet add package Microsoft.Agents.AI.Foundry &#8211;prerelease</li>



<li>dotnet add package Microsoft.AI.Foundry.Local</li>



<li>dotnet run</li>
</ul>



<p class="wp-block-paragraph">Python virtualenv</p>



<ul class="wp-block-list">
<li>python -m venv venv &amp;&amp; source venv/bin/activate</li>



<li>pip install -r requirements.txt</li>



<li>python main.py</li>
</ul>



<p class="wp-block-paragraph">Foundry CLI (example patterns — CLI name may vary)</p>



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



<li>foundryctl catalog inspect phi-4-mini</li>



<li>foundryctl model download phi-4-mini &#8211;path /path/to/models</li>
</ul>



<p class="wp-block-paragraph"><strong>Where to get canonical examples</strong></p>



<ul class="wp-block-list">
<li>Foundry-samples on GitHub: microsoft-foundry/foundry-samples (C# and platform-specific samples)</li>



<li>Agent Framework repo: microsoft/agent-framework (sample projects for C# and Python)</li>



<li>Microsoft Learn Foundry Local docs: (Foundry Local get-started &amp; SDK reference)<br />If any command or API fails, consult those repos for exact, copy-paste-ready Program.cs and main.py.</li>
</ul>



<p class="wp-block-paragraph"><br />Foundry Local + MAF gives you an approachable path to local agents that respect privacy and latency constraints. The main friction is model selection and the first-time download/optimization. Start with the official sample repos (foundry-samples and agent-framework) for exact API versions, try a small model (phi-4-mini) for initial experiments, and iterate to larger/quantized models as your hardware allows</p>



<p class="wp-block-paragraph">Note: The initial draft of this post was written by <a href="https://github.com/JesseLiberty/BlogWriter/blob/main/README.md">BlogWriter</a> and then edited by Jesse Liberty<br />Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ChatClient v ResponsesClient in Microsoft Agent Framework</title>
		<link>https://jesseliberty.com/2026/09/25/chatclient-v-responsesclient-in-microsoft-agent-framework/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 25 Sep 2026 18:46:08 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13808</guid>

					<description><![CDATA[TL;DRUse the Responses client for new projects if you want the newest, richest hosted tools and structured outputs; use the Chat Completion client when you need maximum backward compatibility or are constrained to the Chat-completions API (for example, Go-only paths &#8230; <a href="https://jesseliberty.com/2026/09/25/chatclient-v-responsesclient-in-microsoft-agent-framework/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">TL;DR<br />Use the Responses client for new projects if you want the newest, richest hosted tools and structured outputs; use the Chat Completion client when you need maximum backward compatibility or are constrained to the Chat-completions API (for example, Go-only paths or older models).</p>



<p class="wp-block-paragraph">Why this matters<br />Choosing between a Responses client and a Chat Completion client in Microsoft Agent Framework affects which provider API and hosted tools your agent can use, how easy it is to migrate, and what models/back ends you can target. That can influence development effort, feature availability (hosted code execution, file search, image generation, structured outputs), and runtime portability.</p>



<span id="more-13808"></span>



<p class="wp-block-paragraph"><strong>High-level comparison</strong></p>



<ul class="wp-block-list">
<li>Responses client: wraps the newer Responses API (recommended). Exposes a broad hosted-tool surface (hosted code interpreter, hosted file &amp; web search, image generation, hosted model-completion-providers (MCP), structured/typed outputs, and long-running or background/stateful responses). It is <em>best for modern, full-featured agents and Foundry/Azure OpenAI deployments</em>.</li>



<li>Chat Completion client: wraps the classic Chat Completions API surface. Supports a smaller set of integrations (function-like tools, web search integrations, and local MCPs) and offers broader model/back-end compatibility today.<em> Useful for legacy integrations</em>, older runtimes, or when Go-only support is required.</li>
</ul>



<p class="wp-block-paragraph">Agent-level parity<br />From the Agent Framework developer perspective, both client types can be wrapped into an agent (for example, ChatClientAgent/ChatAgent or AIAgent wrappers). The agent programming model—adding middleware and tools, streaming behavior, sessions, and calling agent.RunAsync—looks the same. The practical differences are primarily the underlying provider API and which tools and structured responses the agent can use.</p>



<p class="wp-block-paragraph"><strong>Feature differences (what you get with Responses vs. Chat Completion)</strong></p>



<ul class="wp-block-list">
<li>Responses client (richer feature set)
<ul class="wp-block-list">
<li>Hosted tools: hosted code execution (Hosted Code Interpreter), hosted file search, hosted web search, hosted MCPs, image generation. This is what we do in the demo program <a href="https://github.com/jesseliberty/blogwriter">BlogWriter</a>.</li>



<li>Structured and typed outputs: direct support for structured response formats and schema-like outputs.</li>



<li>Long-lived or background/stateful responses: support for tasks that can run/continue beyond a single request (callbacks, async background work, persistent contexts).</li>



<li>Better integration with Foundry/Azure OpenAI advanced features.</li>
</ul>
</li>



<li>Chat Completion client (compatibility-focused)
<ul class="wp-block-list">
<li>Classic chat-completions features and function-style tools.</li>



<li>Local MCP integrations and web search via function/tools.</li>



<li>Broader compatibility with older models and provider runtime implementations.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><strong>Compatibility and portability</strong></p>



<ul class="wp-block-list">
<li>Choose Chat Completion client when you must target older models or runtimes that do not yet expose the Responses API, or when you need the broadest cross-provider compatibility. Agent Framework’s current Go path is aligned to the Chat Completion client, making it the practical choice for Go-only environments.</li>



<li>Choose Responses client when you deploy to providers that support the Responses API (Foundry, Azure OpenAI) and when you need hosted tools and structured outputs.</li>
</ul>



<p class="wp-block-paragraph"><strong>Tiny runnable examples</strong></p>



<p class="wp-block-paragraph">Responses client (recommended pattern)</p>



<ul class="wp-block-list">
<li>Required using statements:<br />using Azure.AI.OpenAI;<br />using Microsoft.Agents.AI; // (conceptual; align with your Agent Framework namespaces)</li>



<li>Pattern:<br />var client = new OpenAIClient(&#8220;&#8221;);<br />var responsesClient = client.GetResponsesClient();<br />AIAgent agent =<strong> responsesClient.</strong>AsAIAgent(model: &#8220;gpt-4o-mini&#8221;, instructions: &#8220;You are a helpful coding assistant.&#8221;);<br />var result = await agent.RunAsync(&#8220;Write a Python function to sort a list.&#8221;);<br />Console.WriteLine(result);</li>



<li>Expected behavior: prints a Python function that sorts a list. The agent can also invoke hosted tools (if enabled) for code execution, file search, etc.</li>
</ul>



<p class="wp-block-paragraph">C# — Chat Completion client</p>



<ul class="wp-block-list">
<li>Pattern:<br />var client = new OpenAIClient(&#8220;&#8221;);<br />var chatClient = client.GetChatClient(&#8220;gpt-4o-mini&#8221;);<br />AIAgent agent = <strong>chatClient</strong>.AsAIAgent(instructions: &#8220;You are good at telling jokes.&#8221;);<br />var result = await agent.RunAsync(&#8220;Tell me a joke about a pirate.&#8221;);<br />Console.WriteLine(result);</li>



<li>Expected behavior: prints a joke about a pirate. Tooling is limited to chat-completions-style integrations.</li>
</ul>



<p class="wp-block-paragraph"><strong>Migration and practical gotchas</strong></p>



<ul class="wp-block-list">
<li>Feature parity is not 1:1. If you migrate from Chat Completion to Responses, confirm the hosted tools you need exist and behave the same (especially structured outputs and background tasks).</li>



<li>Streaming and rate limits: Responses may differ in streaming semantics and rate limits per provider; test streaming handlers and error handling.</li>



<li>Tool availability: Not all providers expose the same hosted tools. Verify Foundry/Azure OpenAI capabilities in your deployment target.</li>



<li>Structured outputs: If your app relies on function-like JSON outputs, test parsing and schema enforcement under Responses.</li>
</ul>



<p class="wp-block-paragraph"><strong>Conclusion / recommendation</strong><br />For new projects, prefer the Responses client: it unlocks hosted tools, structured outputs, and modern provider features. Use the Chat Completion client when backward compatibility, legacy models, or Go-only constraints dictate it. Because both client types integrate under the same agent abstraction, migrating is mostly about swapping the client and validating tool-specific behaviors</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>