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

<channel>
	<title>Jesse Liberty</title>
	<atom:link href="https://jesseliberty.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://jesseliberty.com</link>
	<description>Senior AI Engineer/ Author/ Podcaster/ Videocaster</description>
	<lastBuildDate>Sun, 20 Sep 2026 14:16:19 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.5</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>Writing A Front End</title>
		<link>https://jesseliberty.com/2026/09/20/writing-a-front-end/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Sun, 20 Sep 2026 14:16:17 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13748</guid>

					<description><![CDATA[For the past six weeks I&#8217;ve been developing my demonstration program as a console application. Today I decided to give it a nice front end using Blazer (I don&#8217;t know Blazer). In the time it took to listen to Tommy &#8230; <a href="https://jesseliberty.com/2026/09/20/writing-a-front-end/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For the past six weeks I&#8217;ve been developing<a href="https://github.com/JesseLiberty/BlogWriter"> my demonstration program</a> as a console application. Today I decided to give it a nice front end using Blazer (I don&#8217;t know Blazer). In the time it took to listen to Tommy (The Who, 1969) and half of The Wall (Pink Floyd, 1979) I had my first, second, and third drafts. That represents almost 30,000 lines of new code and documentation in well under 3 hours.</p>



<figure class="wp-block-image size-large is-resized"><img fetchpriority="high" decoding="async" width="587" height="800" src="https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-587x800.jpg" alt="" class="wp-image-13751" style="aspect-ratio:0.7337447997031192;width:426px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-587x800.jpg 587w, https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-220x300.jpg 220w, https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-110x150.jpg 110w, https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-768x1047.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-1127x1536.jpg 1127w, https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2-1502x2048.jpg 1502w, https://jesseliberty.com/wp-content/uploads/2026/09/blogwriter-2-2.jpg 1834w" sizes="(max-width: 587px) 100vw, 587px" /></figure>



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



<p class="wp-block-paragraph">The bulk of the work was completed in the first round. In the second, I added the minimum and maximum boxes, and in the third, I added the requirement that the entire history of the reviewers&#8217; comments appear in the Review box. </p>



<p class="wp-block-paragraph">I opted to go through the entire Spec Kit process even for the smaller features, which I admit was a bit slower than I would have liked. But all in all I got a starter front end that, again, would have taken <em>much</em> longer, especially since I don&#8217;t know Blazer (though I know a good bit more now, having read through the code and documentation).</p>



<p class="wp-block-paragraph">I ended up with at least a dozen .razor files and twice as many bootstrap.js (and .min.js) files. Copilot told me how to fire it up and what port it would appear on. By starting the application in the terminal, I could watch all my logging and tracing go by, though I added the logging to the bottom of the page (right now it is in a scrolling box, but I think I&#8217;ll change it to display just one line at a time—not sure about that.)</p>



<p class="wp-block-paragraph">My experience was excellent, but I did notice that it was eating a <em>lot</em> of tokens. From the time I started the work until I was done, it had consumed about 10,000 tokens! (ouch)</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Adding Cosmos with Spec Kit &#038; Copilot</title>
		<link>https://jesseliberty.com/2026/09/18/adding-cosmos-with-spec-kit-copilot/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 18 Sep 2026 13:23:24 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13716</guid>

					<description><![CDATA[Yesterday I set out to add Cosmos to my demonstration project. The goal was to keep track of all queries, even after the browser is closed. My plan was to work with Spec Kit and Copilot, as described in my &#8230; <a href="https://jesseliberty.com/2026/09/18/adding-cosmos-with-spec-kit-copilot/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Yesterday I set out to add Cosmos to my<a href="https://github.com/JesseLiberty/BlogWriter"> demonstration project</a>. The goal was to keep track of all queries, even after the browser is closed. My plan was to work with Spec Kit and Copilot, as described in my<a href="https://jesseliberty.com/2026/09/17/spec-driven-development/"> previous blog post</a>. I didn&#8217;t know enough about working with Cosmos, but I was not intimidated; I had help.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="755" height="728" src="https://jesseliberty.com/wp-content/uploads/2026/09/confused-programmer.jpg" alt="" class="wp-image-13717" style="aspect-ratio:1.037077347264548;width:340px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/confused-programmer.jpg 755w, https://jesseliberty.com/wp-content/uploads/2026/09/confused-programmer-300x289.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/confused-programmer-150x145.jpg 150w" sizes="(max-width: 755px) 100vw, 755px" /></figure>



<p class="wp-block-paragraph">The first step was to create a specification, saying what I wanted to build. (I reused the existing Constitution).</p>



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



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



<p class="wp-block-paragraph"><em>Replace the local JSON-only session store with a Cosmos DB for NoSQL-backed&nbsp;<code>IBlogSessionStore</code>&nbsp;implementation. Persist each complete&nbsp;<code>BlogSession</code>&nbsp;under its signed-in Microsoft Entra object ID, expose a console command that lists the owner&#8217;s 20 newest sessions, and retain the existing&nbsp;<code>resume &lt;session-id&gt;</code>&nbsp;and follow-up workflows. Use optimistic concurrency so a stale client cannot overwrite a newer saved session.</em></p>



<p class="wp-block-paragraph">Actually, even that statement was written by Copilot. My initial statement was a bit sparser. In any case, with that in place, I could write my plan. When I did so, Spec Kit asked a number of clarifying questions to make sure it knew exactly what I wanted.</p>



<p class="wp-block-paragraph"><em>Q: How should the application identify the owner of each saved session?</em><br /><br />It created a series of User Stories to ensure that I agreed with the acceptance criteria. With that done, it created a series of phased tasks and their implementation dependencies (e.g., you can&#8217;t do User Story 2 until you&#8217;ve finished the work for User Story 1).</p>



<p class="wp-block-paragraph">I&#8217;m simplifying because this was covered in the previous blog post.</p>



<p class="wp-block-paragraph">Spec Kit documented the research it did and the decisions it made, along with its rationale.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="748" height="731" src="https://jesseliberty.com/wp-content/uploads/2026/09/aha-1.jpg" alt="" class="wp-image-13734" style="aspect-ratio:1.023243049755399;width:376px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/aha-1.jpg 748w, https://jesseliberty.com/wp-content/uploads/2026/09/aha-1-300x293.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/aha-1-150x147.jpg 150w" sizes="(max-width: 748px) 100vw, 748px" /></figure>



<h2 class="wp-block-heading"><em>Decision: Reuse a single Cosmos client and use asynchronous APIs</em></h2>



<p class="wp-block-paragraph"><em><strong>Rationale</strong>: Reusing the client avoids connection churn, and asynchronous cancellation-aware operations match the existing store contract. Cosmos SDK retry behavior handles transient throttling; conflict responses remain user-actionable and are not retried as writes.</em></p>



<p class="wp-block-paragraph"><em><strong>Alternatives considered</strong>:</em></p>



<ul class="wp-block-list">
<li><em>Construct a client per operation: adds latency and risks socket exhaustion</em>.</li>



<li><em>Retry every write: may hide stale-write conflicts and violate the specified reload behavior.</em></li>
</ul>



<p class="wp-block-paragraph">It was ready to list the tasks that would break down the work to be done. As part of creating the tasks, it created tasks.md, which listed what had to be done and which would contain boxes to check off as it proceeded.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="435" src="https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2-800x435.jpg" alt="" class="wp-image-13739" style="aspect-ratio:1.8391164486382157;width:548px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2-800x435.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2-300x163.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2-150x82.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2-768x417.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2-1536x835.jpg 1536w, https://jesseliberty.com/wp-content/uploads/2026/09/user-story-2.jpg 1969w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">Very cool. I set it free to write the code. It touched or added 17 files, not counting the documentation. It took a while to review all that code, but it was very well commented, and I didn&#8217;t have to make any changes.</p>



<p class="wp-block-paragraph">I then asked it to provision a Cosmos database on Foundry, which it did, using my pre-existing resource</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="438" src="https://jesseliberty.com/wp-content/uploads/2026/09/foundry-1-800x438.jpg" alt="" class="wp-image-13742" style="aspect-ratio:1.8265036633157268;width:522px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/foundry-1-800x438.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/foundry-1-300x164.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/foundry-1-150x82.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/foundry-1-768x421.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/foundry-1.jpg 1388w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">With this, I can observe the entries into the database. For each entry I can get the search request and its related metadata</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="419" src="https://jesseliberty.com/wp-content/uploads/2026/09/metadata-800x419.jpg" alt="" class="wp-image-13719" style="aspect-ratio:1.9092973550627839;width:523px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/metadata-800x419.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/metadata-300x157.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/metadata-150x79.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/metadata-768x402.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/metadata-1536x805.jpg 1536w, https://jesseliberty.com/wp-content/uploads/2026/09/metadata.jpg 1607w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">It also added the &#8220;list&#8221; prompt to the console. When I click list, it displays the last 20 (configurable) queries. </p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="344" src="https://jesseliberty.com/wp-content/uploads/2026/09/list-800x344.jpg" alt="" class="wp-image-13720" style="aspect-ratio:2.3255776114546047;width:416px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/list-800x344.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/list-300x129.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/list-150x64.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/list-768x330.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/list.jpg 1341w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">All of this took two to three hours—and then only because I carefully read all the code as I went (and took one or two phone calls and made a cup of coffee). I would guess that this represented a 5-10x productivity improvement. And I ended up with extensive documentation, better than I could (or would) have written myself.</p>



<p class="wp-block-paragraph">Next step: right now to resume a session, you have to enter the session ID (a GUID). I will have it number the entries (e.g., [1]) and then recall that session based on that number.</p>



<p class="wp-block-paragraph"><strong><em>I love programming; I&#8217;ve been doing it for over 30 years (Assembly, C, C++, C#) but I don&#8217;t imagine I&#8217;ll be doing much keyboard banging any more. Yes, you need to know the language and idioms to check the code, but with this kind of productivity improvement it seems irresponsible to cling to the old way of creating applications</em></strong>.</p>



<p class="wp-block-paragraph">Statement of the week related to the above: <em>No one ever wanted a ½</em>&#8221; <em>drill bit</em>. <em>What they want is a ⅛</em>&#8221; <em>hole.</em></p>



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



<p class="wp-block-paragraph">I grew impatient, so I put in another hour&#8217;s work with Spec Kit and Copilot and implemented the feature allowing the user to pick a number from the list and hiding the GUID</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="267" src="https://jesseliberty.com/wp-content/uploads/2026/09/numbered-800x267.jpg" alt="" class="wp-image-13723" style="aspect-ratio:2.9964360587002097;width:551px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/numbered-800x267.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/numbered-300x100.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/numbered-150x50.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/numbered-768x257.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/numbered.jpg 1397w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">Click one of the bracketed numbers, and that search becomes current. You can then run it again or modify it (and the database will update to reflect the modification).</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Spec Driven Development (SDD)</title>
		<link>https://jesseliberty.com/2026/09/17/spec-driven-development/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Thu, 17 Sep 2026 11:25:19 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13699</guid>

					<description><![CDATA[I am totally blown away by the combination of Spec Kit and Copilot. It is one thing to read about SDD and quite another to build with it. [For more on the steps in SDD, please see what I wrote &#8230; <a href="https://jesseliberty.com/2026/09/17/spec-driven-development/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I am totally blown away by the combination of Spec Kit and Copilot. It is one thing to read about SDD and quite another to build with it. </p>



<p class="wp-block-paragraph">[For more on the steps in SDD, please see what I <a href="https://jesseliberty.com/wp-admin/post.php?post=13577&amp;action=edit">wrote </a>about Spec Kit previously.]</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="258" height="247" src="https://jesseliberty.com/wp-content/uploads/2026/09/spec-kit-mind-blown.jpg" alt="" class="wp-image-13700" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/spec-kit-mind-blown.jpg 258w, https://jesseliberty.com/wp-content/uploads/2026/09/spec-kit-mind-blown-150x144.jpg 150w" sizes="auto, (max-width: 258px) 100vw, 258px" /></figure>



<p class="wp-block-paragraph">Today I fired up Spec Kit and pointed Copilot at it, instructing Copilot to create a constitution based on the existing code (my blog writing demo). A minute later I had a comprehensive document that listed all the immutable characteristics of the application. It was quite long, but here is the beginning:</p>



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



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<h3 class="wp-block-heading">Hosted-Agent Boundaries</h3>



<p class="wp-block-paragraph">The console application is responsible for workflow orchestration and session handling. Blogger, Researcher, Author, and Reviewer remain independently deployable Azure AI Foundry Hosted Agents. The console must communicate with them through Microsoft Agent Framework abstractions and must not issue ad hoc raw HTTP calls or recreate their model logic in-process.</p>
</blockquote>



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



<ul class="wp-block-list">
<li>Microsoft Agent Framework-Native Workflow Composition</li>



<li>Identity, Secrets, and Budget Control</li>



<li>Testable and Observable Behavior</li>



<li>Simple, Compatible Evolution</li>



<li>Technical Constraints</li>



<li>Development Workflow</li>



<li>Governance</li>
</ul>



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



<p class="wp-block-paragraph">With that in hand, I wrote a basic specification of what I wanted to build, and Copilot worked with Spec Kit to turn it into a specification document (in about 5 seconds). I wrote:<br /><em>Add long-term memory using Cosmos so that we can store what is currently in the session to allow listing of previous questions and allow follow-up</em></p>



<p class="wp-block-paragraph">Spec Kit turned that into a full specification and identified a series of questions that needed clarification. It presented each to me with multiple-choice answers, including the ability for me to add a custom choice. For example, one area that needed clarification was:<br /><br />Q: What should happen to saved sessions when a user leaves the organization or their Entra account is deleted? -&gt; A: Automatically delete sessions when the account is deleted.</p>



<p class="wp-block-paragraph">The specification also included:</p>



<ul class="wp-block-list">
<li>A series of user stories with their acceptance criteria</li>



<li>A List of edge cases</li>



<li>A list of functional requirements</li>



<li>A list of key entities</li>



<li>A list of success criteria with measurable outcomes</li>



<li>A list of assumptions</li>
</ul>



<p class="wp-block-paragraph">And so it went through the various stages of SDD. It created a comprehensive plan for my approval and then wrote out all the tasks to implement that plan. As it went, it checked off each item in the task list, driven by unit tests.</p>



<p class="wp-block-paragraph">When I was done, I had a fully working, fully documented new feature that would, in the past, have taken three days and today took two or three hours. And the only reason it took that long was that I was carefully checking its work, which was nearly flawless.</p>



<p class="wp-block-paragraph">For details, I recommend reviewing the code for the <a href="https://github.com/JesseLiberty/BlogWriter">demonstration application</a>.</p>



<p class="wp-block-paragraph">More on this to come&#8230;</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Zoom Presentation</title>
		<link>https://jesseliberty.com/2026/09/16/zoom-presentation/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Wed, 16 Sep 2026 20:34:10 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Presentations]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13696</guid>

					<description><![CDATA[Click here]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="800" height="349" src="https://jesseliberty.com/wp-content/uploads/2026/09/lansing-800x349.jpg" alt="" class="wp-image-13697" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/lansing-800x349.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/lansing-300x131.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/lansing-150x66.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/lansing-768x335.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/lansing.jpg 1092w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">Click <a href="https://www.meetup.com/glugnet/events/315721661/?slug=glugnet&amp;eventId=315721661">here</a></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Important for programmers to remember</title>
		<link>https://jesseliberty.com/2026/09/15/important-for-programmers-to-remember/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 15 Sep 2026 20:22:00 +0000</pubDate>
				<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13693</guid>

					<description><![CDATA[No one ever wanted a ⅛ inch drill bit. What they want is a ⅛ inch hole.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">No one ever wanted a ⅛ inch drill bit. What they want is a ⅛ inch hole.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Microsoft Agent Framework: Human In The Loop</title>
		<link>https://jesseliberty.com/2026/09/14/microsoft-agent-framework-human-in-the-loop/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Mon, 14 Sep 2026 21:02:37 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13689</guid>

					<description><![CDATA[tl;dr If you’re building automated workflows that sometimes must pause for human judgment (approvals, moderation, triage), Microsoft Agent Framework provides first‑class HITL primitives: typed RequestPorts, durable suspended execution, emitted RequestInfoEvents, and automatic routing of responses back to the correct workflow &#8230; <a href="https://jesseliberty.com/2026/09/14/microsoft-agent-framework-human-in-the-loop/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">tl;dr <br />If you’re building automated workflows that sometimes must pause for human judgment (approvals, moderation, triage), Microsoft Agent Framework provides first‑class HITL primitives: typed RequestPorts, durable suspended execution, emitted RequestInfoEvents, and automatic routing of responses back to the correct workflow executor.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="398" src="https://jesseliberty.com/wp-content/uploads/2026/09/Human-in-the-loop-2-800x398.jpg" alt="" class="wp-image-13690" style="aspect-ratio:2.0101256328520534;width:378px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/Human-in-the-loop-2-800x398.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/09/Human-in-the-loop-2-300x149.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/Human-in-the-loop-2-150x75.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/Human-in-the-loop-2-768x382.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/09/Human-in-the-loop-2.jpg 1158w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p class="wp-block-paragraph">This article explains the runtime mechanics (what happens when a workflow asks a human for input), the developer experience (what you write), concrete examples (C# + JSON contracts), integration patterns with Bot Framework/contact centers, failure modes, and testing strategies.</p>



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



<p class="wp-block-paragraph"><strong>A concise scenario</strong><br />An LLM suggests a $50,000 refund to a customer. If your automation approves it automatically you risk fraud and compliance violations; if you block it you frustrate customers and increase manual work. The correct middle ground is a durable, auditable workflow that pauses, sends a precise task to a human reviewer, and reliably resumes once the reviewer responds. Microsoft Agent Framework makes that pattern a core primitive instead of an ad‑hoc add‑on.</p>



<p class="wp-block-paragraph"><strong>Overview: what Agent Framework gives you for HITL</strong></p>



<ul class="wp-block-list">
<li>Typed RequestPorts: define strongly typed request and response schemas so humans and automation agree on payload shapes.</li>



<li>Durable suspend/resume: when an executor issues a request, the workflow branch is suspended durably until a response arrives (survives restarts).</li>



<li>RequestInfoEvent emission: the framework emits a structured event the agent hub or any subscriber can consume to present tasks to humans.</li>



<li>Automatic routing &amp; correlation: responses include metadata that the framework uses to match the response to the exact waiting executor, avoiding much manual correlation code.</li>



<li>Orchestration &amp; handoff primitives: the workflow language supports approval nodes, escalations, timeouts, and integration points for agent hubs or contact centers.</li>
</ul>



<p class="wp-block-paragraph"><strong>Request lifecycle: step‑by‑step</strong></p>



<p class="wp-block-paragraph">Declare RequestPort(s) in the workflow</p>



<ol class="wp-block-list"></ol>



<ul class="wp-block-list">
<li>A RequestPort is a typed channel (request type, response type). Example: RequestPort.</li>
</ul>



<p class="wp-block-paragraph">Executor issues a request</p>



<ol class="wp-block-list"></ol>



<ul class="wp-block-list">
<li>Inside an executor (HandleAsync or equivalent), the code calls the framework API (e.g., context.SendRequestAsync) to send the request payload. The call returns only when the response is received or the request is cancelled/timeout occurs.</li>
</ul>



<p class="wp-block-paragraph">Workflow branch is suspended</p>



<ol class="wp-block-list"></ol>



<ul class="wp-block-list">
<li>The framework persists the suspended state. The executor essentially awaits the response; the workflow instance remains durable in storage (so node restarts or transient failures won’t lose the wait).</li>
</ul>



<p class="wp-block-paragraph">RequestInfoEvent is emitted</p>



<ol class="wp-block-list"></ol>



<ul class="wp-block-list">
<li>The framework publishes an event that includes request metadata, the typed payload, timestamps, TTL, and any contextual fields the workflow author supplied. External systems subscribe and surface the task to human operators.</li>
</ul>



<p class="wp-block-paragraph">Human reviews and posts a response</p>



<ol class="wp-block-list"></ol>



<ul class="wp-block-list">
<li>The human operator uses an agent hub UI (built-in or custom) to inspect context and submit a response. The agent hub posts the typed response back to the Agent Framework through a Response Ingest API or SDK call.</li>
</ul>



<p class="wp-block-paragraph">Framework routes response back to waiting executor</p>



<ol class="wp-block-list"></ol>



<ul class="wp-block-list">
<li>Using request id + workflow instance id (and port name/type), the framework finds the suspended executor and delivers the typed response. The executor resumes and the workflow continues.</li>
</ul>



<p class="wp-block-paragraph">Developer view: what you author<br />At a high level you:</p>



<ul class="wp-block-list">
<li>Define RequestPorts and response types.</li>



<li>Write executors that call context.SendRequestAsync (or RequestPort.Send).</li>



<li>Subscribe to RequestInfoEvents or integrate Event Grid/Service Bus to route tasks to your agent hub/UIs.</li>



<li>Implement a response ingest endpoint (secured) that posts typed responses back to the framework.</li>
</ul>



<p class="wp-block-paragraph"><strong>Concise C# worked example (end‑to‑end sketch)</strong><br />Below is a minimal, runnable‑style illustration (API names adapted to the docs’ conceptual forms). This shows the RequestPort declaration, the executor sending a request, emitting an event handled by the agent hub, and the response ingest flow.</p>



<p class="wp-block-paragraph">1) Define request/response types<br />public record ContentModerationRequest(string Content, string UserId, string ConversationId);<br />public record ContentModerationResponse(bool Approved, string ReviewerId, string Notes);</p>



<p class="wp-block-paragraph">2) Create a RequestPort and wire into a workflow<br />var contentModerationPort = RequestPort.Create(&#8220;ContentModeration&#8221;);</p>



<p class="wp-block-paragraph">var workflow = new WorkflowBuilder(contentModerationPort)<br />.AddEdge(inputSource, contentModerationExecutor)<br />.AddEdge(contentModerationExecutor, continueExecutor)<br />.Build();</p>



<p class="wp-block-paragraph">3) Executor sends request and awaits response<br /></p>



<pre class="wp-block-code"><code>internal sealed class ContentModerationExecutor : Executor
{
   public override async ValueTask HandleAsync(ContentItem message, IWorkflowContext context, CancellationToken ct)
   {
      var request = new ContentModerationRequest(message.Text, message.UserId, message.ConversationId);
    // SendRequestAsync emits a RequestInfoEvent and suspends until the human response arrives
    ContentModerationResponse response = await context.SendRequestAsync(contentModerationPort, request, ct);

    if (response.Approved)
    {
        // proceed with publishing, optionally using edited content from notes
        await context.ScheduleAsync(new PublishMessage(response.ReviewerId, message.Id), ct);
    }
    else
    {
        // handle rejection path
        await context.ScheduleAsync(new NotifyUserDecline(message.UserId), ct);
    }
  }
}</code></pre>



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



<ul class="wp-block-list">
<li>SendRequestAsync returns a typed response object (ContentModerationResponse) when the human submits their decision.</li>



<li>While awaiting, the workflow&#8217;s state is persisted so the process is durable.</li>
</ul>



<p class="wp-block-paragraph">Agent hub responsibilities and the RequestInfoEvent<br />When SendRequestAsync is called, the framework emits a RequestInfoEvent. The agent hub subscribes (Event Grid, Service Bus, or a SDK event callback) and must implement:</p>



<ul class="wp-block-list">
<li>Task display: show request details, provenance (workflow id, request id), attachments, and relevant context (conversation transcript, policy guidance).</li>



<li>Claiming model: accept/claim or pick-from-queue semantics to avoid double-work.</li>



<li>Response posting: a secure call to the Response Ingest API that returns success or a clear error (with appropriate HTTP status codes).</li>
</ul>



<p class="wp-block-paragraph">Sample RequestInfoEvent payload (JSON)<br />{<br />&#8220;requestId&#8221;: &#8220;req-abc123&#8221;,<br />&#8220;workflowInstanceId&#8221;: &#8220;wf-987&#8221;,<br />&#8220;portName&#8221;: &#8220;ContentModeration&#8221;,<br />&#8220;requestPayload&#8221;: {<br />&#8220;content&#8221;: &#8220;AI-generated reply…&#8221;,<br />&#8220;userId&#8221;: &#8220;user-42&#8221;,<br />&#8220;conversationId&#8221;: &#8220;conv-77&#8221;<br />},<br />&#8220;createdAt&#8221;: &#8220;2026-09-14T12:34:56Z&#8221;,<br />&#8220;ttlSeconds&#8221;: 3600,<br />&#8220;metadata&#8221;: { &#8220;priority&#8221;: &#8220;high&#8221;, &#8220;origin&#8221;: &#8220;chatbot-01&#8221; }<br />}</p>



<p class="wp-block-paragraph">Response ingest contract (what the agent hub posts back)<br />POST /api/agent/responses<br />Headers:<br />Authorization: Bearer<br />Body:<br />{<br />&#8220;requestId&#8221;: &#8220;req-abc123&#8221;,<br />&#8220;workflowInstanceId&#8221;: &#8220;wf-987&#8221;,<br />&#8220;responsePayload&#8221;: {<br />&#8220;approved&#8221;: true,<br />&#8220;reviewerId&#8221;: &#8220;agent-007&#8221;,<br />&#8220;notes&#8221;: &#8220;Approved; removed PII&#8221;<br />},<br />&#8220;submittedAt&#8221;: &#8220;2026-09-14T12:40:01Z&#8221;<br />}</p>



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



<ul class="wp-block-list">
<li>200 OK — response accepted and routed to the executor.</li>



<li>400 Bad Request — malformed payload or schema mismatch.</li>



<li>404 Not Found — request id not found (possibly expired or already completed).</li>



<li>409 Conflict — duplicate or conflicting response (depending on your policy).</li>



<li>410 Gone — request timed out or was cancelled.</li>
</ul>



<p class="wp-block-paragraph"><strong>Correlation, routing, and metadata best practices</strong><br />What the framework provides:</p>



<ul class="wp-block-list">
<li>requestId: unique per request invocation.</li>



<li>workflowInstanceId: identifies the workflow instance.</li>



<li>portName (and port type): identifies the RequestPort.<br />With those, most routing is automatic. Practical guidance:</li>



<li>Always include requestId in your response ingest calls. requestId is the primary, unambiguous handle for routing.</li>



<li>Port name/type adds another safety layer, useful if your agent hub sees many types of requests.</li>



<li>Capture and surface workflowInstanceId in the agent UI for audit and troubleshooting.</li>



<li>Store and emit reviewer identity and timestamps in responses for compliance.</li>
</ul>



<p class="wp-block-paragraph"><strong>Handling duplicates, late responses, and concurrency</strong></p>



<ul class="wp-block-list">
<li>First‑wins vs quorum: by default a single response satisfies the waiting executor. If you need multiple approvals or consensus, model it explicitly—create multiple RequestPorts or a response type that supports aggregation and implement quorum logic in the workflow.</li>



<li>Duplicate submissions: agent hub should make the ingest POST idempotent. The server should accept the first valid response and return 409 or 200 for subsequent duplicates, depending on your semantics.</li>



<li>Late arrivals: responses for cancelled or timed‑out requests should be rejected with 410 Gone and the agent hub should surface a clear explanation and allow re‑issuing (create a new request).</li>



<li>Concurrency: if two human agents submit different responses simultaneously, the framework decides based on request id ordering; design the workflow to verify critical transitions—e.g., accept only responses signed by an approver role, or require a reconciliation step.</li>
</ul>



<p class="wp-block-paragraph"><strong>Timeouts, fallbacks, and alternate flows</strong><br />Long human latency blocks user experience. Your workflows should:</p>



<ul class="wp-block-list">
<li>Configure sensible TTLs on RequestPorts (e.g., 30m for routine approvals, 24h for slower approvals, two minutes if the user is on hold!).</li>



<li>Define fallback flows for timeouts: reassign to escalation queue, auto‑approve/deny under defined policies, or create a notification to a manager.</li>



<li>Emit interim events (e.g., “pending too long”) to monitoring and alerting systems.</li>
</ul>



<p class="wp-block-paragraph"><strong>Orchestration patterns: single approval, parallel approvals, and SAGA compensation</strong></p>



<ul class="wp-block-list">
<li>Single approval: the workflow issues one request and continues when it receives a single response.</li>



<li>Parallel approvals (n of m or quorum): create multiple requests in parallel and wait for N responses or a consensus step. Example:</li>



<li>Spawn three RequestPorts (or the same port with multiple request instances) and gather responses. Resume when at least two are approvals.</li>



<li>SAGA / compensation: if the human rejects a step that has already triggered side effects, model compensating actions in the workflow (reverse a debit, revoke a provisional publish). Combine timeouts with compensation to avoid leaving inconsistent state.</li>
</ul>



<p class="wp-block-paragraph"><strong>Integrating with Bot Framework and contact centers</strong><br />Agent Framework HITL and Bot Framework are complementary:</p>



<ul class="wp-block-list">
<li>Bot as trigger: a conversational bot collects context and triggers a workflow that may create a RequestPort task for human approval.</li>



<li>Bot as proxy: once a human accepts, Bot Framework’s handoff patterns (bot-as-proxy) can connect a human agent into the live conversation while the workflow continues to manage business-level approvals.</li>



<li>Agent hub integration: use Omnichannel for Dynamics or an agent hub service to surface tasks to Teams, Dynamics agents, or third‑party contact centers.</li>
</ul>



<p class="wp-block-paragraph"><strong>A concrete flow (bot + Agent Framework + Teams)</strong></p>



<ol class="wp-block-list">
<li>Bot collects issue and calls a workflow API to start a warranty claim workflow.</li>



<li>Workflow evaluates risk. If high, it calls SendRequestAsync on a ClaimApprovalRequestPort.</li>



<li>The Agent Framework emits RequestInfoEvent and routes it to Event Grid.</li>



<li>Event Grid forwards to an agent hub that posts a message in a Teams queue or Dynamics Omnichannel.</li>



<li>An agent reviews in Teams, submits the approval via the agent hub (which calls the Response Ingest API).</li>



<li>Agent Framework resumes the workflow and posts the outcome back to the bot (or directly to the user).</li>
</ol>



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



<ul class="wp-block-list">
<li>Authentication: secure the Response Ingest API with OAuth 2.0 (Azure AD Bearer tokens) or mutual TLS. Agent hub should authenticate as a service principal with least privilege.</li>



<li>Authorization: enforce RBAC so only authorized roles can approve certain request types (finance team for refunds).</li>



<li>PII removal: redact or mask sensitive fields in RequestInfoEvent payloads and in agent UIs. If full PII is required, limit retention and access logs.</li>



<li>Audit trails: persist request/response payloads, reviewer identity, timestamps, and any changes. Use immutable storage or append‑only logs for compliance.</li>
</ul>



<p class="wp-block-paragraph"><strong>Observability and operational metrics</strong><br />Track and alert on:</p>



<ul class="wp-block-list">
<li>Pending request count per port.</li>



<li>Average and P95 human response time.</li>



<li>SLA (Service Level Agreement) breaches (e.g., pending_response_time > SLA).</li>



<li>Request churn (creates, cancels, re‑issues).</li>



<li>Approval rates and reviewer throughput.</li>
</ul>



<p class="wp-block-paragraph"><strong>Note </strong>P95 indicates 95th percental latency between when a task is presented to a human and when they respond.</p>



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



<ul class="wp-block-list">
<li>Pending requests > threshold for > X minutes (escalate).</li>



<li>Average response time rising > 2x baseline (investigate agent hub throughput/availability).</li>



<li>Rejected requests spike > typical baseline (potential model drift or policy change).</li>
</ul>



<p class="wp-block-paragraph"><strong>Testing and CI/CD strategies</strong></p>



<ul class="wp-block-list">
<li>Unit testing executors: mock SendRequestAsync to simulate human responses and assert branching logic.</li>



<li>Integration tests: test full path — executor sends request → event emitted → simulate agent hub response → workflow resumes.</li>



<li>Load testing agent hub: simulate spikes of RequestInfoEvent to confirm queueing behavior and UI scaling.</li>



<li>Chaos testing: simulate delayed responses, duplicate responses, and timeout conditions to verify fallback flows and compensation logic.</li>
</ul>



<p class="wp-block-paragraph"><strong>Common pitfalls and how to avoid them</strong></p>



<ul class="wp-block-list">
<li>Missing context in the agent UI: always surface the workflowInstanceId, requestId, and relevant transcript attachments. Humans need context to make fast decisions.</li>



<li>Assuming low latency: design timeouts and fallback behavior; don’t block user interfaces waiting indefinitely.</li>



<li>Weak correlation design: require requestId in responses. Avoid relying on human-entered identifiers that can be mistyped.</li>



<li>Lack of auditability: store reviewer identity and notes; consider signing responses or using immutable logs for compliance.</li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Using Session State</title>
		<link>https://jesseliberty.com/2026/09/12/using-session-state/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Sat, 12 Sep 2026 12:48:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13662</guid>

					<description><![CDATA[My demo program is a multi-agent application that accepts a topic BlogAgent, researches that topic (ResearcherAgent) and then drafts a blog post (AuthorAgent). The post is then reviewed by another agent (ReviewerAgent) who either accepts the draft or sends it &#8230; <a href="https://jesseliberty.com/2026/09/12/using-session-state/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">My demo program is a multi-agent application that accepts a topic BlogAgent, researches that topic (ResearcherAgent) and then drafts a blog post (AuthorAgent). The post is then reviewed by another agent (ReviewerAgent) who either accepts the draft or sends it back to the Author with requested changes.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="784" height="748" src="https://jesseliberty.com/wp-content/uploads/2026/09/extend-search.jpg" alt="" class="wp-image-13667" style="aspect-ratio:1.0481544854558789;width:332px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/extend-search.jpg 784w, https://jesseliberty.com/wp-content/uploads/2026/09/extend-search-300x286.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/extend-search-150x143.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/extend-search-768x733.jpg 768w" sizes="auto, (max-width: 784px) 100vw, 784px" /></figure>



<p class="wp-block-paragraph">I wanted to allow the user to refine the search after getting the draft. To do so, I needed short term memory. Here are the changes I made&#8230;</p>



<p class="wp-block-paragraph">I implemented the search refinement flow by updating the research state and wiring it into the research step:</p>



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



<ul class="wp-block-list">
<li>In <code>ResearchState.cs</code>, I added:
<ul class="wp-block-list">
<li><code>CurrentSubTask</code> to represent the newest follow-up request</li>



<li><code>SearchRefinements</code> to keep the history of prior refinement requests</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">SearchRefinements is a list&lt;string>. As the ResearchState is created the code checks to see if there are refinements. If so the new refinements are appended to the existing request</p>



<pre class="wp-block-code"><code>       if (SearchRefinements.Count > 0)
        {
            string uniqueRefinements = string.Join(" | ", SearchRefinements
                .Where(r => !string.IsNullOrWhiteSpace(r))
                .Distinct(StringComparer.OrdinalIgnoreCase));

            if (!string.IsNullOrWhiteSpace(uniqueRefinements))
            {
                parts.Add($"Refinement history: {uniqueRefinements}");
            }
        }
</code></pre>



<ul class="wp-block-list">
<li><code>BuildResearchQuery()</code> to assemble a single, richer research prompt that includes:
<ul class="wp-block-list">
<li>the original task</li>



<li>the active follow-up refinement</li>



<li>previous refinement history</li>



<li>prior research findings/context</li>
</ul>
</li>



<li><code>StartFollowUp()</code> to preserve the existing draft/research state while resetting the review cycle and recording the previous subtask as a refinement</li>
</ul>



<pre class="wp-block-code"><code>   public void StartFollowUp(string followUp)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(followUp);

        string trimmed = followUp.Trim();
        if (!string.IsNullOrWhiteSpace(CurrentSubTask) &amp;&amp;
            !string.Equals(CurrentSubTask, trimmed, StringComparison.OrdinalIgnoreCase))
        {
            SearchRefinements.Add(CurrentSubTask.Trim());
        }

        SearchRefinements.Add(trimmed);
        CurrentSubTask = trimmed;
        ReviewNotes = "";
        RevisionNumber = 0;
        NextStep = "";
    }
</code></pre>



<ul class="wp-block-list">
<li>In <code>ResearcherAgent.cs</code>, I changed the research node to use <code>state.BuildResearchQuery()</code> for each research pass, so the agent searches with both the original goal and the refinement history instead of starting from scratch.</li>
</ul>



<pre class="wp-block-code"><code>    public string BuildResearchQuery()
    {
        var parts = new List&lt;string>();

        if (!string.IsNullOrWhiteSpace(MainTask))
        {
            parts.Add($"Original task: {MainTask.Trim()}");
        }

        if (!string.IsNullOrWhiteSpace(CurrentSubTask))
        {
            parts.Add($"Follow-up refinement: {CurrentSubTask.Trim()}");
        }

        if (SearchRefinements.Count > 0)
        {
            string uniqueRefinements = string.Join(" | ", SearchRefinements
                .Where(r => !string.IsNullOrWhiteSpace(r))
                .Distinct(StringComparer.OrdinalIgnoreCase));

            if (!string.IsNullOrWhiteSpace(uniqueRefinements))
            {
                parts.Add($"Refinement history: {uniqueRefinements}");
            }
        }

        if (ResearchFindings.Count > 0)
        {
            string context = string.Join("\n\n", ResearchFindings
                .Where(f => !string.IsNullOrWhiteSpace(f))
                .Select(f => f.Trim()));

            if (!string.IsNullOrWhiteSpace(context))
            {
                parts.Add($"Prior research context:\n{context}");
            }
        }

        return string.Join("\n\n", parts);
    }</code></pre>



<ul class="wp-block-list">
<li>In <code>ResearchStateTests.cs</code>, I added coverage confirming that:
<ul class="wp-block-list">
<li>a follow-up preserves the original task</li>



<li>the refinement is included in the generated search query</li>



<li>earlier research context is retained</li>
</ul>
</li>
</ul>



<pre class="wp-block-code"><code>   &#91;Fact]
    public void StartFollowUp_PreservesDraftAndResearchButResetsReviewCycle()
    {
        var state = new ResearchState
        {
            MainTask = "topic",
            ResearchFindings = &#91;"finding"],
            Draft = "draft",
            ReviewNotes = ResearchState.ApprovedMarker,
            RevisionNumber = ResearchState.MaxRevisions,
            NextStep = "END",
        };

        state.StartFollowUp("Add a caching section.");

        Assert.Equal("Add a caching section.", state.CurrentSubTask);
        Assert.Equal("draft", state.Draft);
        Assert.Equal(&#91;"finding"], state.ResearchFindings);
        Assert.Empty(state.ReviewNotes);
        Assert.Equal(0, state.RevisionNumber);
        Assert.Empty(state.NextStep);
    }

    &#91;Fact]
    public void BuildResearchQuery_IncludesOriginalTopicAndFollowUpRefinement()
    {
        var state = new ResearchState
        {
            MainTask = "How to build a blog app",
            ResearchFindings = &#91;"Concepts: context, prompts, and workflow.", "Drafting is done after research."],
            CurrentSubTask = "Add a caching section."
        };

        string query = state.BuildResearchQuery();

        Assert.Contains("How to build a blog app", query);
        Assert.Contains("Add a caching section.", query);
        Assert.Contains("Concepts: context, prompts, and workflow.", query);
        Assert.Contains("Drafting is done after research.", query);
    }</code></pre>



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



<h2 class="wp-block-heading">Why this enables refining a search</h2>



<p class="wp-block-paragraph">The crucial behavior is that a follow-up no longer discards the earlier work. The generated query now looks like:</p>



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



<li>Follow-up refinement</li>



<li>Prior refinement history</li>



<li>Earlier research findings</li>
</ul>



<p class="wp-block-paragraph">That means the next search is “narrowed and contextualized” rather than replaced, which is the actual refinement behavior you were asking for.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Copilot Review To The Rescue</title>
		<link>https://jesseliberty.com/2026/09/12/copilot-review-to-the-rescue/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Sat, 12 Sep 2026 10:30:48 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13673</guid>

					<description><![CDATA[For those who are following along with the evolution of my Blog Writing application: after the last revision (allowing follow-up questions to a query) Copilot evaluated my Pull Request and sent me an email that the follow-up was not, in &#8230; <a href="https://jesseliberty.com/2026/09/12/copilot-review-to-the-rescue/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For those who are following along with the evolution of my Blog Writing application: after the last revision (allowing follow-up questions to a query) Copilot evaluated my Pull Request and sent me an email that the follow-up was not, in fact, being added to the research stage. This was because the original research was in place and so that step was skipped.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="779" height="736" src="https://jesseliberty.com/wp-content/uploads/2026/09/fix-code.jpg" alt="" class="wp-image-13674" style="aspect-ratio:1.0584518167456556;width:356px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/fix-code.jpg 779w, https://jesseliberty.com/wp-content/uploads/2026/09/fix-code-300x283.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/09/fix-code-150x142.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/09/fix-code-768x726.jpg 768w" sizes="auto, (max-width: 779px) 100vw, 779px" /></figure>



<p class="wp-block-paragraph">Copilot and I fixed this with the following changes:</p>



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



<p class="wp-block-paragraph">Refined follow-ups now reset stale state so the workflow re-enters research instead of reusing old draft/research data.</p>



<p class="wp-block-paragraph">Updated in ResearchState.cs:80-105<br />This clears the draft, research findings, review notes, and revision state before the next run.<br />The blogger routing now correctly sees “no research” after a refinement and sends the next step back to the researcher.</p>



<p class="wp-block-paragraph">The logic is in BloggerAgent.cs:24-68<br />The behavior is covered by BloggerAgentRoutingTests.cs:34-58<br />The researcher’s console output now prints the full query, including the original task and the follow-up refinement, rather than just the bare subtask label.</p>



<p class="wp-block-paragraph">Updated in ResearcherAgent.cs:69-84<br />We also added regression tests for both the reset behavior and the routing behavior:</p>



<p class="wp-block-paragraph">This cycle of using Copilot to generate code and tests, reviewing that code, posting a PR to GitHub, getting back feedback from Copilot and, as needed, making revisions and creating another PR is a powerful development cycle.</p>



<p class="wp-block-paragraph">For more on this see <a href="https://jesseliberty.com/2026/09/07/copilot-code-review/">Copilot Code Review</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Podcast: MAF Deep Dive</title>
		<link>https://jesseliberty.com/2026/09/11/podcast-maf-deep-dive/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 11 Sep 2026 18:05:46 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Podcast]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13669</guid>

					<description><![CDATA[Happy to post to my podcast a deep dive into Microsoft Agent Framework with Daniel Costea. Available here or wherever you get your podcasts.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Happy to post to my podcast a deep dive into Microsoft Agent Framework with Daniel Costea. Available <a href="https://jesseliberty.fireside.fm">here</a> or wherever you get your podcasts.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Microsoft Tenants</title>
		<link>https://jesseliberty.com/2026/09/11/microsoft-tenants/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 11 Sep 2026 14:52:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Essentials]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13630</guid>

					<description><![CDATA[What is an Azure tenant (Microsoft Entra ID tenant) — and what is it used for? tl;drAn Azure tenant — now called a Microsoft Entra ID tenant — is a dedicated, isolated instance of Microsoft’s identity service that represents an &#8230; <a href="https://jesseliberty.com/2026/09/11/microsoft-tenants/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">What is an Azure tenant (Microsoft Entra ID tenant) — and what is it used for?</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="593" height="800" src="https://jesseliberty.com/wp-content/uploads/2026/09/tenant-593x800.jpg" alt="" class="wp-image-13631" style="aspect-ratio:0.7412449461903553;width:334px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/09/tenant-593x800.jpg 593w, https://jesseliberty.com/wp-content/uploads/2026/09/tenant-222x300.jpg 222w, https://jesseliberty.com/wp-content/uploads/2026/09/tenant-111x150.jpg 111w, https://jesseliberty.com/wp-content/uploads/2026/09/tenant.jpg 706w" sizes="auto, (max-width: 593px) 100vw, 593px" /></figure>



<p class="wp-block-paragraph">tl;dr<br />An Azure tenant — now called a Microsoft Entra ID tenant — is a dedicated, isolated instance of Microsoft’s identity service that represents an organization’s identity and access boundary. <strong>It’s the directory that stores users, groups, applications and their credentials, service principals, roles, and policies used to authenticate and authorize access to resources and apps</strong>.</p>



<p class="wp-block-paragraph">Why the distinction matters<br />People often confuse &#8220;tenant&#8221; with &#8220;subscription.&#8221; They are different:</p>



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



<ul class="wp-block-list">
<li>Tenant (Microsoft Entra ID): an identity and access container. It owns the directory objects (users, groups, app registrations, service principals, directory roles, conditional access policies).</li>



<li>Subscription: a billing and resource container for Azure resources (VMs, storage, databases). A subscription is associated with a tenant (one tenant), but a tenant can contain many subscriptions and management groups. In short: tenant = identity; subscription = resources and billing.</li>
</ul>



<p class="wp-block-paragraph"><strong>What an Entra ID tenant is used for (practical overview)</strong></p>



<ul class="wp-block-list">
<li>Identity and authentication</li>



<li>Manage user accounts, passwords, multifactor authentication (MFA), single sign-on (SSO), and passwordless sign-in across Microsoft and third-party apps.</li>



<li>App registration and authorization</li>



<li>Register applications, create service principals, and issue tokens for apps and APIs. This is how applications are granted permissions and obtain tokens to call Microsoft Graph or Azure management APIs.</li>



<li>Access control</li>



<li>Assign Azure RBAC and directory (Entra) roles to users, groups, and service principals to govern who can access resources and what actions they can perform.</li>



<li>External identities and collaboration</li>



<li>Invite partner users (B2B), provide customer identity flows (B2C), or support multi-tenant SaaS applications that let users from other Entra tenants sign in.</li>



<li>Governance and compliance</li>



<li>Attach subscriptions to the tenant’s management groups, apply policies and compliance controls across your organization, and centralize identity governance (for example, Conditional Access and identity lifecycle controls).</li>
</ul>



<p class="wp-block-paragraph"><strong>Trends and the bigger picture</strong></p>



<ul class="wp-block-list">
<li>Branding: Azure Active Directory has been rebranded into Microsoft Entra ID (part of the Entra family). The rename doesn’t change the underlying tenant semantics, but highlights identity as a broader product family.</li>



<li>Identity-first Zero Trust: Entra ID is increasingly the policy decision point for Zero Trust — evaluating risk signals, applying Conditional Access and enforcing least privilege.</li>



<li>SaaS multi-tenant patterns: Microsoft has published more guidance and reference architectures for secure multi-tenant identity and tenant-scoped configuration.</li>
</ul>



<p class="wp-block-paragraph"><strong>Common challenges and best practices</strong></p>



<ul class="wp-block-list">
<li>Tenant sprawl and governance
<ul class="wp-block-list">
<li>  Problem: unmanaged extra tenants make centralized governance hard.
<ul class="wp-block-list">
<li>  Best practice: maintain a clear subscription-to-tenant mapping, use management groups and policies, and minimize unnecessary tenants.</li>
</ul>
</li>
</ul>
</li>



<li>Secure app credentials
<ul class="wp-block-list">
<li>  Problem: long-lived client secrets leaking leads to compromise.
<ul class="wp-block-list">
<li>  Best practice: prefer managed identities and certificates over client secrets in production, rotate credentials, and minimize permissions.</li>
</ul>
</li>
</ul>
</li>



<li>Problem: multi-tenant app design affects consent, provisioning, and data isolation.
<ul class="wp-block-list">
<li>   Best practice: decide early between single-tenant vs multi-tenant approach and design authentication/authorization flows and provisioning patterns accordingly.</li>
</ul>
</li>



<li>Principle of least privilege
<ul class="wp-block-list">
<li>   Assign roles with minimal necessary permissions and use group-based or role-based assignments rather than granting broad directory admin roles.</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph"><strong>How to find your tenant ID</strong></p>



<ul class="wp-block-list">
<li>Azure/Entra admin center: Microsoft Entra (or Azure Active Directory) &gt; Properties (or Overview) shows the Tenant ID (GUID).</li>



<li>Azure CLI:</li>



<li>az account show &#8211;query tenantId -o tsv</li>



<li>PowerShell (Az module):</li>



<li>Get-AzTenant | Select-Object TenantId, Domain<br />These values are used in code and configuration (for example, building token endpoints or constructing credentials).</li>
</ul>



<p class="wp-block-paragraph"><strong>When to create a new tenant</strong></p>



<ul class="wp-block-list">
<li>You typically create a new tenant for a distinct organization or when you need a fully isolated identity boundary (separate security/policy scopes, compliance reasons, or entirely separate administrative domains). Avoid creating tenants just to work around permissions or billing; in many cases management groups, subscriptions, and RBAC can provide the needed separation.</li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
