<?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>Tue, 21 Jul 2026 22:29:32 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.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>Activity Source in Microsoft Agent Framework</title>
		<link>https://jesseliberty.com/2026/07/21/activity-source-in-microsoft-agent-framework/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 21 Jul 2026 18:35:23 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13434</guid>

					<description><![CDATA[In the rapidly evolving landscape of artificial intelligence, the ability to monitor and debug multi-agent systems is paramount. The Microsoft Agent Framework (MAF) has taken a significant step forward by integrating OpenTelemetry, a powerful observability framework that enhances the monitoring &#8230; <a href="https://jesseliberty.com/2026/07/21/activity-source-in-microsoft-agent-framework/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p>In the rapidly evolving landscape of artificial intelligence, the ability to monitor and debug multi-agent systems is paramount. The Microsoft Agent Framework (MAF) has taken a significant step forward by integrating OpenTelemetry, a powerful observability framework that enhances the monitoring capabilities of AI agents. This post delves into the key features, innovations, and real-world applications of this integration, providing developers with the insights they need to optimize their AI systems.</p>



<figure class="wp-block-image size-full is-resized"><img fetchpriority="high" decoding="async" width="767" height="446" src="https://jesseliberty.com/wp-content/uploads/2026/07/image-4.png" alt="" class="wp-image-13435" style="aspect-ratio:1.7197689808687282;width:359px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/07/image-4.png 767w, https://jesseliberty.com/wp-content/uploads/2026/07/image-4-300x174.png 300w, https://jesseliberty.com/wp-content/uploads/2026/07/image-4-150x87.png 150w" sizes="(max-width: 767px) 100vw, 767px" /></figure>



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



<h2 class="wp-block-heading">Understanding the Microsoft Agent Framework and OpenTelemetry</h2>



<p>The Microsoft Agent Framework is designed to facilitate the development of intelligent agents that can interact with users and other systems. With the integration of OpenTelemetry, MAF now offers a robust solution for tracking and analyzing agent behavior, performance, and interactions. OpenTelemetry is a set of APIs, libraries, and tools that enable developers to collect and export telemetry data, such as logs and metrics, from their applications. This integration allows for a more comprehensive understanding of how AI agents operate in real-world scenarios.</p>



<h3 class="wp-block-heading">Key Trends and Innovations</h3>



<h4 class="wp-block-heading">1. Unified Observability</h4>



<p>One of the standout features of the MAF&#8217;s integration with OpenTelemetry is the support for OpenTelemetry conventions. This allows for a unified timeline of tasks across different agents and tools, which is crucial for understanding the interactions and performance of AI agents in production environments. By providing a coherent view of agent activities, developers can more easily identify patterns and correlations that may impact performance.</p>



<h4 class="wp-block-heading">2. Instrumentation Layer</h4>



<p>The MAF includes an automatic instrumentation layer that tracks various metrics, such as token consumption, interaction durations, and tool usage. This layer is essential for diagnosing issues and optimizing agent performance. By automatically capturing these metrics, developers can focus on building and improving their agents rather than spending time on manual logging and monitoring.</p>



<h4 class="wp-block-heading">3. Integration with Azure</h4>



<p>Another significant advantage of the MAF&#8217;s integration with OpenTelemetry is its ability to ship traces to Azure AI Foundry. This centralized monitoring and analysis capability allows teams to gain insights into agent behavior across different environments. By leveraging Azure&#8217;s powerful analytics tools, developers can visualize performance metrics and identify areas for improvement.</p>



<h3 class="wp-block-heading">Real-World Use Cases</h3>



<p>The integration of OpenTelemetry into the Microsoft Agent Framework opens up a range of practical applications for developers:</p>



<ul class="wp-block-list">
<li><strong>Debugging and Evaluation</strong>: Developers can utilize OpenTelemetry to trace agent interactions, making it easier to identify bottlenecks or failures in real-time. This capability is invaluable for ensuring that agents perform as expected and for quickly addressing any issues that arise.</li>



<li><strong>Performance Monitoring</strong>: By tracking metrics such as latency and interaction counts, teams can optimize their agents for better user experiences. Understanding how agents perform under different conditions allows developers to make informed decisions about enhancements and adjustments.</li>
</ul>



<h3 class="wp-block-heading">Supporting Data and Quotes</h3>



<p>The importance of OpenTelemetry in enhancing observability is underscored by insights from industry experts. According to a <a href="https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/observability-for-multi-agent-systems-with-microsoft-agent-framework-and-azure-a/4469090">Microsoft Tech Community blog post</a>, &#8220;OpenTelemetry for traces/spans + attributes (agent, tool, retrieval, latency, tokens)&#8221; enables detailed monitoring of agent activities. This level of granularity is essential for developers looking to improve their systems.</p>



<p>As of now, the framework is in public preview (version 1.0.0-rc1), with stable OpenTelemetry contracts guiding its development. This means that developers can start experimenting with these features and provide feedback to shape the future of the framework.</p>



<h3 class="wp-block-heading">A Simple Explanation of OpenTelemetry</h3>



<p>OpenTelemetry can be thought of as a toolkit that helps developers collect and export telemetry data from their applications. In the context of the Microsoft Agent Framework, it allows developers to track how their AI agents are performing, making it easier to spot issues and improve functionality. This is particularly important in complex systems where multiple agents interact with each other and with users.</p>



<h3 class="wp-block-heading">Code Example: Using OpenTelemetry with the Microsoft Agent Framework</h3>



<p>To illustrate how developers can leverage OpenTelemetry within the Microsoft Agent Framework, consider the following C# code example. This snippet demonstrates how to start an activity for an agent session and log interactions using OpenTelemetry:</p>



<pre class="wp-block-code"><code>using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Extensions.Logging;

public class Agent
{
    private static readonly ActivitySource activitySource = new ActivitySource("Microsoft.Extensions.AI");

    public void StartSession(string sessionId, ILogger logger)
    {
        logger.LogInformation("Starting agent session with ID: {SessionId}", sessionId);
        using (var activity = activitySource.StartActivity("Agent Session"))
        {
            int interactionCount = 0;
            while (true)
            {
                Console.Write("You (or 'exit' to quit): ");
                var input = Console.ReadLine();
                if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
                {
                    logger.LogInformation("User requested to exit the session");
                    break;
                }
                interactionCount++;
                logger.LogInformation("Processing interaction #{InteractionCount}", interactionCount);
                using (var interactionActivity = activitySource.StartActivity("Agent Interaction"))
                {
                    // Simulate processing the input
                }
            }
        }
    }
}</code></pre>



<p>In this example, the <code>StartSession</code> method initiates an agent session and logs each interaction. Each interaction is tracked as a child span, allowing for detailed performance analysis. This approach not only enhances observability but also provides developers with the tools they need to optimize their agents effectively.</p>



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



<p>The integration of OpenTelemetry into the Microsoft Agent Framework represents a significant advancement in the monitoring and debugging of AI agents. By leveraging these tools, developers can gain valuable insights into their systems, leading to improved performance and user satisfaction. </p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Logging &amp; OpenTelemetry  in MAF</title>
		<link>https://jesseliberty.com/2026/07/21/logging-opentelemetry-in-maf/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 21 Jul 2026 13:31:47 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13431</guid>

					<description><![CDATA[In the rapidly evolving landscape of software development, particularly in the realm of artificial intelligence and automation, the importance of robust logging mechanisms cannot be overstated. This is especially true for multi-agent systems, where numerous agents interact and collaborate to &#8230; <a href="https://jesseliberty.com/2026/07/21/logging-opentelemetry-in-maf/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p>In the rapidly evolving landscape of software development, particularly in the realm of artificial intelligence and automation, the importance of robust logging mechanisms cannot be overstated. This is especially true for multi-agent systems, where numerous agents interact and collaborate to achieve complex tasks. In this post, we will explore the significance of logging in a multi-agent Microsoft Agent Framework application, the trends and innovations in logging practices, real-world use cases, and a practical implementation example.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="761" height="686" src="https://jesseliberty.com/wp-content/uploads/2026/07/image-3.png" alt="" class="wp-image-13432" style="aspect-ratio:1.1093317465655965;width:300px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/07/image-3.png 761w, https://jesseliberty.com/wp-content/uploads/2026/07/image-3-300x270.png 300w, https://jesseliberty.com/wp-content/uploads/2026/07/image-3-150x135.png 150w" sizes="(max-width: 761px) 100vw, 761px" /></figure>



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



<h2 class="wp-block-heading">Importance of Logging</h2>



<h3 class="wp-block-heading">1. Debugging and Monitoring</h3>



<p>Logging serves as a vital tool for debugging and monitoring the behavior of agents within a multi-agent system. Given the complexity of interactions among agents, it is crucial to have a clear understanding of their operations. Logs provide insights into the flow of operations, enabling developers to identify issues and rectify them promptly. For instance, if an agent fails to respond to a user query, logs can help trace back the steps leading to the failure, allowing for quicker resolution.</p>



<h3 class="wp-block-heading">2. Performance Metrics</h3>



<p>In addition to aiding in debugging, logging is instrumental in gathering performance metrics. By logging various performance indicators, developers can analyze the efficiency of agents and optimize their operations. This is particularly important in systems where performance directly impacts user experience. For example, logging response times can help identify slow agents, leading to targeted optimizations that enhance overall system performance.</p>



<h3 class="wp-block-heading">3. Audit Trails</h3>



<p>Logging also plays a critical role in maintaining audit trails. In many applications, especially those dealing with sensitive data or compliance requirements, it is essential to have a historical record of actions taken by agents. This not only aids in accountability but also ensures that organizations can meet regulatory requirements. Logs can provide a detailed account of agent interactions, decisions made, and actions taken, which is invaluable during audits.</p>



<h2 class="wp-block-heading">Key Trends and Innovations</h2>



<h3 class="wp-block-heading">Centralized Logging</h3>



<p>One of the best practices in modern software development is the implementation of centralized logging systems. Centralized logging aggregates logs from multiple agents into a single repository, making it easier to analyze and monitor the entire system. This approach simplifies the process of identifying issues that may arise from interactions between agents, as developers can view all relevant logs in one place.</p>



<h3 class="wp-block-heading">OpenTelemetry Integration</h3>



<p>The Microsoft Agent Framework supports OpenTelemetry, a set of APIs, libraries, agents, and instrumentation to provide observability for applications. By integrating OpenTelemetry for tracing and logging, developers can create a coherent timeline of agent interactions and performance metrics. This integration allows for more sophisticated monitoring and analysis, enabling developers to gain deeper insights into their systems.</p>



<h2 class="wp-block-heading">Real-World Use Cases</h2>



<h3 class="wp-block-heading">Agent Interaction Tracking</h3>



<p>In customer service applications, logging can be used to track how agents respond to user queries. By analyzing these logs, organizations can identify patterns in agent behavior, assess the effectiveness of different response strategies, and ultimately improve customer satisfaction. For instance, if logs reveal that certain types of queries consistently lead to longer response times, organizations can provide additional training or resources to agents handling those queries.</p>



<h3 class="wp-block-heading">Performance Analysis</h3>



<p>In logistics and supply chain management, multi-agent systems are often employed to optimize workflows. Logging can help identify bottlenecks in these workflows by providing insights into agent performance. For example, if logs indicate that a particular agent is consistently slow in processing tasks, developers can investigate the underlying causes and implement optimizations to enhance efficiency.</p>



<h2 class="wp-block-heading">Implementation Example</h2>



<p>To illustrate how logging can be implemented in a Microsoft Agent Framework application, consider the following example using C# and OpenTelemetry. This example demonstrates a simple agent that logs its activities during a session.</p>



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

public class Agent
{
    private readonly ILogger&lt;Agent&gt; _logger;
    private readonly ActivitySource _activitySource;

    public Agent(ILogger&lt;Agent&gt; logger)
    {
        _logger = logger;
        _activitySource = new ActivitySource("AgentActivitySource");
    }

    public void StartSession(string sessionId)
    {
        _logger.LogInformation("Starting agent session with ID: {SessionId}", sessionId);
        using (var activity = _activitySource.StartActivity("Agent Session"))
        {
            // Simulate agent interactions
            for (int i = 0; i &lt; 5; i++)
            {
                ProcessInteraction(i);
            }
        }
    }

    private void ProcessInteraction(int interactionCount)
    {
        _logger.LogInformation("Processing interaction #{InteractionCount}", interactionCount);
        // Simulate processing logic
    }
}</code></pre>



<h3 class="wp-block-heading">Explanation of the Code</h3>



<p>In this example, we define an <code>Agent</code> class that utilizes the <code>ILogger</code> interface for logging. The <code>StartSession</code> method initiates a new session and logs the session ID. Within this method, we create an <code>Activity</code> using <code>ActivitySource</code>, which allows us to trace the session&#8217;s activities. The <code>ProcessInteraction</code> method simulates processing interactions and logs each interaction count.</p>



<p>This simple implementation showcases how logging can be seamlessly integrated into an agent&#8217;s workflow, providing valuable insights into its operations.</p>



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



<p>Adding logging to a multi-agent application using the Microsoft Agent Framework is essential for effective monitoring, debugging, and performance analysis. By leveraging centralized logging and tools like OpenTelemetry, developers can gain valuable insights into their systems, leading to improved agent performance and user satisfaction. As multi-agent systems continue to grow in complexity, the importance of robust logging practices will only increase, making it a critical component of successful application development.</p>



<p>Incorporating logging not only enhances the reliability of multi-agent systems but also empowers developers to make informed decisions based on real-time data, ultimately driving innovation and efficiency in their applications.</p>



<p>In the next blog post I&#8217;ll add this to the Blogger application we saw <a href="https://jesseliberty.com/2026/06/28/migrating-c-microsoft-agent-framework/">here</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Which Pilot is Which?</title>
		<link>https://jesseliberty.com/2026/07/20/which-pilot-is-which/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Mon, 20 Jul 2026 18:38:52 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Tools and Utilities]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13425</guid>

					<description><![CDATA[Microsoft can make you crazy by naming so many products with overlapping titles: Copilot, Copilot Studio, GitHub Copilot, and GitHub Copilot App In the rapidly evolving landscape of software development and productivity tools, understanding the distinctions between various Microsoft AI-assisted &#8230; <a href="https://jesseliberty.com/2026/07/20/which-pilot-is-which/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p>Microsoft can make you crazy by naming so many products with overlapping titles: Copilot, Copilot Studio, GitHub Copilot, and GitHub Copilot App</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="752" height="719" src="https://jesseliberty.com/wp-content/uploads/2026/07/image-2.png" alt="" class="wp-image-13429" style="aspect-ratio:1.0459057979852184;width:396px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/07/image-2.png 752w, https://jesseliberty.com/wp-content/uploads/2026/07/image-2-300x287.png 300w, https://jesseliberty.com/wp-content/uploads/2026/07/image-2-150x143.png 150w" sizes="(max-width: 752px) 100vw, 752px" /></figure>



<p>In the rapidly evolving landscape of software development and productivity tools, understanding the distinctions between various Microsoft AI-assisted applications is crucial for developers and users alike. This post will delve into the differences between Copilot, Copilot Studio, GitHub Copilot, and the GitHub Copilot App, highlighting their unique features, use cases, and how they can enhance your workflow.</p>



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



<h2 class="wp-block-heading">1. GitHub Copilot</h2>



<p><strong>GitHub Copilot</strong> is an AI-powered code completion tool developed by GitHub in collaboration with OpenAI. It functions as a virtual pair programmer, providing real-time suggestions for code snippets, functions, and even entire algorithms based on the context of the code being written. Integrated into popular Integrated Development Environments (IDEs) such as Visual Studio Code and JetBrains IDEs, GitHub Copilot is designed to streamline the coding process.</p>



<h3 class="wp-block-heading">Key Features:</h3>



<ul class="wp-block-list">
<li><strong>Contextual Suggestions:</strong> GitHub Copilot analyzes the surrounding code and offers relevant code completions, making it easier for developers to write code efficiently.</li>



<li><strong>Multi-language Support:</strong> It supports a wide range of programming languages, including C#, Python, JavaScript, TypeScript, Ruby, and more, catering to diverse development needs.</li>



<li><strong>Learning from Open Source:</strong> Trained on a vast dataset of public code repositories, GitHub Copilot can suggest best practices and common coding patterns.</li>
</ul>



<h3 class="wp-block-heading">Use Case:</h3>



<p>A developer working on a web application can leverage GitHub Copilot to quickly generate boilerplate code, implement functions, or debug existing code, significantly accelerating the development process.</p>



<h2 class="wp-block-heading">2. GitHub Copilot App</h2>



<p>The <strong>GitHub Copilot App</strong> is a standalone application that provides a user-friendly interface for interacting with GitHub Copilot&#8217;s features. This app allows users to access Copilot&#8217;s functionalities outside of traditional IDEs, making it easier to integrate AI assistance into various workflows.</p>



<h3 class="wp-block-heading">Key Features:</h3>



<ul class="wp-block-list">
<li><strong>User-Friendly Interface:</strong> The app offers a more accessible way to interact with Copilot&#8217;s suggestions, making it suitable for users who prefer a visual interface rather than working a the CLI. </li>



<li><strong>Integration with GitHub:</strong> It seamlessly connects with GitHub repositories, allowing users to pull in code and suggestions directly from their projects.</li>
</ul>



<h3 class="wp-block-heading">Use Case:</h3>



<p>A developer who prefers a more visual approach can utilize the GitHub Copilot App to manage coding tasks, receive suggestions, and collaborate with team members without being confined to a specific IDE.</p>



<h2 class="wp-block-heading">3. Copilot Studio</h2>



<p><strong>Copilot Studio</strong> is a more advanced platform that empowers developers and non-developers to create and customize their own AI solutions in a low-code or no-code development platform. It provides tools for building tailored AI applications that can integrate with various APIs and processes, enabling users to develop specific functionalities that meet their unique needs.</p>



<h3 class="wp-block-heading">Key Features:</h3>



<ul class="wp-block-list">
<li><strong>Customization:</strong> Users can create custom AI models and workflows tailored to their specific requirements, allowing for greater flexibility in application development.</li>



<li><strong>API Integration:</strong> Copilot Studio supports calling external APIs and processes, facilitating more complex interactions and functionalities.</li>
</ul>



<h3 class="wp-block-heading">Use Case:</h3>



<p>A data scientist might use Copilot Studio to build a custom AI model that analyzes data from a specific source, providing insights and recommendations based on that data.</p>



<h2 class="wp-block-heading">4. Microsoft 365 Copilot</h2>



<p><strong>Microsoft 365 Copilot</strong> focuses on enhancing productivity within the Microsoft 365 suite (Word, Excel, PowerPoint, etc.). This tool uses AI to assist users in automating tasks, generating content, and summarizing information.</p>



<h3 class="wp-block-heading">Key Features:</h3>



<ul class="wp-block-list">
<li><strong>Office Integration:</strong> Microsoft 365 Copilot works within Microsoft Office applications to streamline workflows and enhance user experience.</li>



<li><strong>Task Automation:</strong> It helps automate repetitive tasks, allowing users to focus on more critical aspects of their work.</li>
</ul>



<h3 class="wp-block-heading">Use Case:</h3>



<p>A business professional can utilize Microsoft 365 Copilot to draft emails, create presentations, or analyze data in Excel, significantly improving productivity.</p>



<h2 class="wp-block-heading">Summary of Differences</h2>



<ul class="wp-block-list">
<li><strong>Scope of Use:</strong> GitHub Copilot is specifically designed for coding, while Copilot Studio allows for use by a broader audience. The GitHub Copilot App serves as a user-friendly interface for GitHub Copilot, and Microsoft 365 Copilot focuses on productivity within the Microsoft Office ecosystem.</li>



<li><strong>Integration:</strong> GitHub Copilot integrates with IDEs, while the GitHub Copilot App provides a standalone experience. Microsoft 365 Copilot integrates with Office applications to enhance productivity.</li>
</ul>



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



<p>While all these tools share the common goal of enhancing productivity through AI, they cater to different audiences and use cases. GitHub Copilot and its app focus on coding assistance, Copilot Studio allows for low-code AI development, and Microsoft 365 Copilot enhances productivity in office applications. </p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Ensuring Agent Safety in AI Development</title>
		<link>https://jesseliberty.com/2026/07/10/ensuring-agent-safety-in-ai-development/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 10 Jul 2026 20:27:57 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13414</guid>

					<description><![CDATA[Key Insights from Microsoft Learn* As artificial intelligence continues to evolve, ensuring the safety and security of AI agents has become more important. The Microsoft Learn documentation on agent safety provides a comprehensive overview of best practices and guidelines for &#8230; <a href="https://jesseliberty.com/2026/07/10/ensuring-agent-safety-in-ai-development/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading">Key Insights from Microsoft Learn*</h3>



<p>As artificial intelligence continues to evolve, ensuring the safety and security of AI agents has become more important. The Microsoft Learn documentation on agent safety provides a comprehensive overview of best practices and guidelines for developers. </p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="699" height="748" src="https://jesseliberty.com/wp-content/uploads/2026/07/image-1.png" alt="" class="wp-image-13416" style="aspect-ratio:0.9345087612239561;width:237px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/07/image-1.png 699w, https://jesseliberty.com/wp-content/uploads/2026/07/image-1-280x300.png 280w, https://jesseliberty.com/wp-content/uploads/2026/07/image-1-140x150.png 140w" sizes="auto, (max-width: 699px) 100vw, 699px" /></figure>



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



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



<p>The concept of shared responsibility is central to agent safety. While the Microsoft Agent Framework offers foundational tools and abstractions, it is ultimately the responsibility of application developers to ensure secure data flows. This includes validating inputs, configuring tools appropriately, and understanding the specific security needs of their applications.</p>



<h2 class="wp-block-heading">Best Practices for Safe Agents</h2>



<p>To create safe AI agents, developers should adhere to several best practices:</p>



<ul class="wp-block-list">
<li><strong>Deterministic Defenses</strong>: Implement label-based defenses to protect against prompt injection and data exfiltration. This proactive approach helps mitigate risks associated with adversarial inputs.</li>



<li><strong>Data Flow Understanding</strong>: Gain a thorough understanding of how data flows through various components of the agent, including user input, chat history, and context providers. This knowledge is crucial for identifying potential vulnerabilities.</li>
</ul>



<h2 class="wp-block-heading">Data Security Measures</h2>



<p>Robust data security is essential, especially when handling sensitive information such as Personally Identifiable Information (PII). Developers must implement stringent security measures to protect this data throughout its lifecycle.</p>



<h3 class="wp-block-heading">Awareness of Indirect Prompt Injection</h3>



<p>Developers should be vigilant about the risks of indirect prompt injection, where adversarial content can manipulate the behavior of the language model (LLM). Recognizing these risks is vital for maintaining the integrity of AI agents.</p>



<h3 class="wp-block-heading">Integration with Microsoft Security Tools</h3>



<p>The documentation highlights the integration of Microsoft Agent Framework with existing security infrastructures, such as Microsoft Defender and Microsoft Entra. This integration enhances the overall security posture of AI agents, providing additional layers of protection.</p>



<h3 class="wp-block-heading">Real-World Applications</h3>



<p>The insights from the documentation have practical applications&#8230;</p>



<ul class="wp-block-list">
<li><strong>Enterprise AI Operations</strong>: Organizations can leverage AI agents to manage operations at scale, transforming fragmented experimentation into trusted, reliable processes.</li>



<li><strong>Automating Security Tasks</strong>: Tools like Microsoft Security Copilot automate repetitive security tasks, allowing teams to focus on more strategic initiatives.</li>
</ul>



<h3 class="wp-block-heading">Supporting Data and Monitoring</h3>



<p>Centralized visibility into AI agents is crucial for administrators. By monitoring usage and security signals, organizations can proactively manage their AI agents, ensuring they operate safely and effectively.</p>



<p>For more detailed information, refer to the <a href="https://learn.microsoft.com/en-us/agent-framework/agents/safety">Microsoft Learn documentation on Agent Safety</a>.</p>



<p>* This article was generated by a collection of agents I wrote and documented in a <a href="https://jesseliberty.com/2026/06/28/migrating-c-microsoft-agent-framework/">previous blog post</a>, and was then edited by me.</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>MAF-Doctor Walkthrough</title>
		<link>https://jesseliberty.com/2026/07/10/maf-doctor-walkthrough/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 10 Jul 2026 18:49:08 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<category><![CDATA[Utilities]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13411</guid>

					<description><![CDATA[Our latest video is a walkthrough of MAF-Doctor. This is an extraordinary utility for anyone using the Microsoft Agent Framework.]]></description>
										<content:encoded><![CDATA[
<p>Our latest <a href="https://youtube.com/jesseliberty">video</a> is a walkthrough of MAF-Doctor. This is an extraordinary utility for anyone using the Microsoft Agent Framework.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="800" height="540" src="https://jesseliberty.com/wp-content/uploads/2026/07/image-800x540.png" alt="" class="wp-image-13412" srcset="https://jesseliberty.com/wp-content/uploads/2026/07/image-800x540.png 800w, https://jesseliberty.com/wp-content/uploads/2026/07/image-300x202.png 300w, https://jesseliberty.com/wp-content/uploads/2026/07/image-150x101.png 150w, https://jesseliberty.com/wp-content/uploads/2026/07/image-768x518.png 768w, https://jesseliberty.com/wp-content/uploads/2026/07/image.png 1284w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Transparency in Agentics</title>
		<link>https://jesseliberty.com/2026/07/07/transparency-in-agentics/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Tue, 07 Jul 2026 11:42:06 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13407</guid>

					<description><![CDATA[A key requirement in building agentic systems is for their reasoning to be transparent. This allows you to ensure accuracy and to provide &#8220;human in the loop&#8221; oversight. This is also a key requirement for debugging your agents and their &#8230; <a href="https://jesseliberty.com/2026/07/07/transparency-in-agentics/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p>A key requirement in building agentic systems is for their reasoning to be transparent. This allows you to ensure accuracy and to provide &#8220;human in the loop&#8221; oversight. This is also a key requirement for debugging your agents and their interactions.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="253" height="279" src="https://jesseliberty.com/wp-content/uploads/2026/07/transparent-robot.jpg" alt="" class="wp-image-13408" srcset="https://jesseliberty.com/wp-content/uploads/2026/07/transparent-robot.jpg 253w, https://jesseliberty.com/wp-content/uploads/2026/07/transparent-robot-136x150.jpg 136w" sizes="auto, (max-width: 253px) 100vw, 253px" /></figure>



<p>To accomplish this, your agents will need, at a minimum, structured logs of its plans, what tools it calls and an audit trail of the steps it takes.</p>



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



<p>There are four characteristics of valuable transparency:</p>



<ul class="wp-block-list">
<li>The information from the system accurately reflect the actual reasoning</li>



<li>The audit report must be understandable by the reader</li>



<li>The audit report must be updated in a timely manner</li>



<li>There must be actions you can take to adjust the actions and plans as they develop</li>
</ul>



<p>If the information is accurate but you can&#8217;t understand it, it is worthless. Similarly, if it comes too late or there is nothing you can do with it, it is also worthless.</p>



<p>With a good audit trail that tells you step by step what the agent is doing and why, you can view the outcome with a greater degree of trust. However, you must beware of the agent rationalizing its actions after performing them (don&#8217;t we all!). </p>



<p>To avoid thesse pitfalls, log extensively and frequently and make sure the agent is providing step by step rationle for its actions and ask it to surface any uncertainty: &#8220;For every action you take, provide a brief explanation of why you chose it, what the alternatives were and what uncertainties exist.&#8221;</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Migrating C# -&gt; Microsoft Agent Framework</title>
		<link>https://jesseliberty.com/2026/06/28/migrating-c-microsoft-agent-framework/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Sun, 28 Jun 2026 12:33:05 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13378</guid>

					<description><![CDATA[In a previous blog post I ported a Python application to C#. However, I did not take advantage of the Microsoft Agent Framework (MAF). In this admittedly long post, I&#8217;ll migrate that code to MAF. Note, the updated source code &#8230; <a href="https://jesseliberty.com/2026/06/28/migrating-c-microsoft-agent-framework/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p>In a <a href="https://jesseliberty.com/2026/06/22/migrating-agentic-code-python-c-part-6-final/">previous blog post</a> I ported a Python application to C#. However, I did not take advantage of the Microsoft Agent Framework (MAF). In this admittedly long post, I&#8217;ll migrate that code to MAF.</p>



<p><em>Note, the updated source code for the .NET version of this demo application is available at&nbsp;</em><a href="https://github.com/JesseLiberty/blogMigration---public">https://github.com/JesseLiberty/blogMigration—public</a></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="800" height="740" src="https://jesseliberty.com/wp-content/uploads/2026/06/wrench-800x740.jpg" alt="" class="wp-image-13379" style="aspect-ratio:1.081108337745506;width:318px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/06/wrench-800x740.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/06/wrench-300x278.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/06/wrench-150x139.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/06/wrench-768x711.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/06/wrench.jpg 922w" sizes="auto, (max-width: 800px) 100vw, 800px" /></figure>



<p>The <strong>Microsoft Agent Framework (MAF)</strong> is an open-source software development kit (SDK) designed to facilitate the creation of agentic AI solutions and multi-agent workflows, primarily utilizing Python or C#.</p>



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



<h3 class="wp-block-heading">Enterprise Focus</h3>



<p>The framework is tailored to meet the complexities of deploying agentic AI in enterprise settings. It incorporates observability, security, and compliance.</p>



<h3 class="wp-block-heading">Multi-Agent Workflows</h3>



<p>MAF supports advanced orchestration of multiple agents. This capability enables different AI agents to collaborate effectively, which is crucial for applications that demand coordinated efforts across various tasks and functions. This is perfect for the application we&#8217;ve been working on.</p>



<h3 class="wp-block-heading">Integration with Azure</h3>



<p>MAF is deeply integrated with Azure services, allowing developers to harness cloud capabilities for enhanced scalability and performance. This integration ensures that applications built with MAF can efficiently handle varying workloads and user demands.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Agentic AI</strong> refers to AI systems capable of acting autonomously and making decisions based on their environment. The Microsoft Agent Framework simplifies the development and management of these systems by providing a comprehensive set of tools and libraries</p>
</blockquote>



<p>With all that in mind, let&#8217;s look at modifying the raw C# to leverage the MAF&#8230;</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: as part of this port to Microsoft Agent Framework I made good use of the extraordinary utility <a href="https://github.com/joslat/maf-doctor/">MAF-Doctor</a>, which you can learn more about in my video interview with its author <a href="https://www.youtube.com/jesseliberty">here</a>, or the podcast <a href="https://jesseliberty.fireside.fm/">here</a>.</p>
</blockquote>



<p><span style="text-decoration: underline;"><strong>File Changes:</strong></span><br /><br /><strong>Prompts.cs </strong>Split each node&#8217;s prompt into a static instructions constant (the agent&#8217;s system prompt) — removed the {token} placeholder templating.<br /><br /><strong>ResearcherAgent.cs</strong> Now a ChatClientAgent with Tavily attached as a tool — the model searches and summarizes in one run. Deleted ~40 lines of manual tool invocation + JsonDocument parsing + the second summarize call.<br /><br /><strong>BloggerChain.cs</strong> LLM fallback uses structured output (RunAsync) — removed the &#8220;`-fence stripping and JsonSerializer.Deserialize. Deterministic routing preserved; comments clarify the workflow edges are the real router.<br /><br /><strong>AuthorChain.cs</strong> Converted to a ChatClientAgent; role in Instructions, state as the per-turn message.<br /><br /><strong>ReviewerChain.cs </strong>Converted to a ChatClientAgent + correctness fix: a failed review no longer auto-APPROVEDs — it requests revision instead (still bounded by MaxRevisions).<br /><br /><strong>Program.cs</strong> Added .UseFunctionInvocation() to the IChatClient pipeline — required for the researcher&#8217;s tool calls to actually execute.<br /><br /><strong>BlogWorkflow.cs </strong>Switched to RunStreamingAsync + WatchStreamAsync() to stream executor lifecycle events live; identical topology.</p>



<p>Let&#8217;s start with Prompt.cs. The comments show all the changes and the justification:</p>



<pre class="wp-block-code"><code>namespace BlogMigration;

/// &lt;summary&gt;
/// Prompt library for the blog-creation agents.
///
/// MAF idiom change (was: one big template per node mixing role + data):
/// each agent now has a *static* INSTRUCTIONS string (its system prompt / role)
/// that is set once on the &lt;c&gt;ChatClientAgent&lt;/c&gt;, while the *dynamic* state
/// (task, findings, draft, review notes) is passed per-turn as the user message.
/// Separating the durable role from the volatile input is the recommended
/// Microsoft Agent Framework pattern: it keeps the system prompt cacheable,
/// lets the model treat instructions with higher priority than user input,
/// and removes the brittle &lt;c&gt;string.Replace("{token}", ...)&lt;/c&gt; templating.
/// &lt;/summary&gt;
public static class Prompts
{
    /// &lt;summary&gt;
    /// Blogger system prompt. The concrete state is supplied as the user message;
    /// the decision is returned via MAF structured output (typed
    /// &lt;see cref="BloggerDecision"/&gt;), so this prompt no longer needs to describe
    /// the exact JSON shape or beg the model for "no extra text" — the schema is
    /// enforced by the framework.
    /// &lt;/summary&gt;
    public const string BloggerInstructions = """
You are a blogger managing a blog post creation workflow.

Your goal is to ensure a clear, engaging, and valuable blog post targeted at
software developers. Based on the current workflow state provided in the user
message, decide the next step.

Decision Rules:
- If no research exists, choose "researcher"
- If research exists but no draft, choose "author"
- If a draft exists and the reviewer said "APPROVED", choose "END"
- If the draft needs revision, choose "author"
- If revision_number &gt;= 4, choose "END"

Return the next step and a brief task description.
""";

    /// &lt;summary&gt;
    /// Researcher system prompt. The topic to research is supplied as the user
    /// message. The Tavily web-search tool is attached to the agent, so the model
    /// itself decides when to call it and then summarises the results — there is
    /// no longer any hand-written search-call + JSON-parsing orchestration.
    /// &lt;/summary&gt;
    public const string ResearcherInstructions = """
You are a researcher for a technical blog
focused on .NET and AI with examples in C# and Python.

You have access to a web-search tool. Use it to find relevant, up-to-date
insights for the topic given in the user message. Focus on:
- Key trends, challenges, or innovations
- Real-world use cases
- Supporting data or quotes from credible sources
- Simple explanations
- Short code examples in C# or Python

Call the search tool as needed, then summarize your findings concisely.
""";

    /// &lt;summary&gt;
    /// Author system prompt. The task, research findings, current draft and review
    /// notes are supplied as the user message each turn.
    /// &lt;/summary&gt;
    public const string AuthorInstructions = """
You are a professional blogger.

The user message contains the main task, the research findings, the current
draft (if any) and any reviewer notes.

Instructions:
- If this is the first draft (no current draft), create a comprehensive post based on the findings
- If there is a current draft and review notes, revise the draft to address all feedback
- Use a professional tone
- Make the post concise (aim for 250-500 words)

Write the complete post.
""";

    /// &lt;summary&gt;
    /// Reviewer system prompt. The task and the draft to review are supplied as the
    /// user message.
    /// &lt;/summary&gt;
    public const string ReviewerInstructions = """
You are a reviewer evaluating content for a blog post.

The user message contains the main task and the draft to review.

Evaluate the draft based on:
1. Hook Strength – Does the opening grab attention?
2. Clarity – Is the message easy to understand?
3. Value – Does the post offer real insights or lessons?
4. Structure – Are paragraphs short?
5. Tone – Is it authentic and professional?

Respond with one of:
- If the draft is satisfactory (minor issues are okay): "APPROVED - &#91;brief positive comment]"
- If the draft needs improvement: provide specific, actionable feedback for revision
""";
}
</code></pre>



<p>With that in place, we can turn to Researcher. The most important change here is the conversion to a <strong>ChatClientAgent </strong>&#8211; the heart of MAF. Tavily is now attached as a tool. This allowed me to remove about 40 lines of tool invocation and JSON parsing. It also eliminated a second call as shown in the comments:</p>



<pre class="wp-block-code"><code>using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

namespace BlogMigration;

/// &lt;summary&gt;
/// Researcher backed by a Microsoft Agent Framework &lt;see cref="ChatClientAgent"/&gt;.
///
/// MAF idiom change: previously this class manually invoked the Tavily tool,
/// hand-parsed the JSON response, then made a SECOND LLM call to summarise it.
/// Now the Tavily function is registered as a *tool on the agent*, so the model
/// itself decides when to search and produces the summary in a single agent run.
/// This requires the underlying &lt;see cref="IChatClient"/&gt; to have
/// function-invocation middleware enabled (wired in &lt;c&gt;Program.cs&lt;/c&gt; via
/// &lt;c&gt;UseFunctionInvocation()&lt;/c&gt;), which actually executes the tool calls the
/// model requests.
/// &lt;/summary&gt;
public class ResearcherAgent : IResearcherAgent
{
    // The agent is built once and reused for every research turn. It is stateless
    // across turns (no AgentSession is retained), which matches the original
    // per-call behaviour while gaining tool-calling for free.
    private readonly ChatClientAgent _agent;

    public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavilyTool)
    {
        _agent = new ChatClientAgent(llm, new ChatClientAgentOptions
        {
            // Name surfaces in OpenTelemetry traces and agent logs.
            Name = "Researcher",
            ChatOptions = new ChatOptions
            {
                // Static role/system prompt lives here instead of being concatenated
                // into every request body.
                Instructions = Prompts.ResearcherInstructions,
                // Preserve the original sampling/cost settings.
                Temperature = chatOptions.Temperature,
                MaxOutputTokens = chatOptions.MaxOutputTokens,
                // Attaching the tool lets the model call it autonomously.
                Tools = &#91;tavilyTool],
            },
        });
    }

    /// &lt;summary&gt;Execute research by letting the agent search and summarise.&lt;/summary&gt;
    public async Task&lt;string&gt; InvokeAsync(string query)
    {
        try
        {
            // A single agent run: the model may call tavily_search one or more
            // times, read the results, and return a concise summary as its text.
            AgentResponse response = await _agent.RunAsync(query);
            string summary = response.Text;

            return !string.IsNullOrEmpty(summary)
                ? summary
                : $"Research completed on: {query}. Key information has been gathered from web sources.";
        }
        catch (Exception e)
        {
            Console.WriteLine($"Research error: {e.Message}");
            return $"Research completed on: {query}. Key information has been gathered from web sources.";
        }
    }

    /// &lt;summary&gt;Research node that gathers information.&lt;/summary&gt;
    public async Task&lt;ResearchState&gt; ResearchNodeAsync(ResearchState state)
    {
        Console.WriteLine("\n&gt;&gt;&gt;RESEARCHER");

        string subTask = !string.IsNullOrEmpty(state.CurrentSubTask) ? state.CurrentSubTask : state.MainTask;
        Console.WriteLine($"Researching: {subTask}");

        string findings;
        try
        {
            findings = await InvokeAsync(subTask);
            string preview = findings.Length &gt; 100 ? findings&#91;..100] : findings;
            Console.WriteLine($"Found: {preview}...");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Research error: {e.Message}");
            findings = $"Research on {subTask} - information gathered";
        }

        state.ResearchFindings.Add(findings);
        return state;
    }
}
</code></pre>



<p>Let&#8217;s turn to the Author. Again, we convert to ChatClientAgent,</p>



<pre class="wp-block-code"><code>using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

namespace BlogMigration;

/// &lt;summary&gt;
/// Author chain backed by a Microsoft Agent Framework &lt;see cref="ChatClientAgent"/&gt;.
///
/// MAF idiom change: the writing role now lives in the agent's Instructions
/// (set once), and only the volatile state (task, findings, draft, review notes)
/// is sent as the per-turn user message — replacing the previous
/// &lt;c&gt;string.Replace("{token}", ...)&lt;/c&gt; templating against the raw IChatClient.
/// &lt;/summary&gt;
public class AuthorChain : IAuthorChain
{
    private readonly ChatClientAgent _agent;

    public AuthorChain(IChatClient llm, ChatOptions chatOptions)
    {
        _agent = new ChatClientAgent(llm, new ChatClientAgentOptions
        {
            Name = "Author",
            ChatOptions = new ChatOptions
            {
                Instructions = Prompts.AuthorInstructions,
                Temperature = chatOptions.Temperature,
                MaxOutputTokens = chatOptions.MaxOutputTokens,
            },
        });
    }

    public async Task&lt;string&gt; InvokeAsync(ResearchState state)
    {
        List&lt;string&gt; research = state.ResearchFindings;
        string researchText = research.Count &gt; 0 ? string.Join("\n\n", research) : "No research available.";

        // Per-turn input only — the role/instructions are already on the agent.
        string message = $"""
            Main Task: {state.MainTask}

            Research Findings:
            {researchText}

            Current Draft: {(string.IsNullOrEmpty(state.Draft) ? "(none — write the first draft)" : state.Draft)}

            Review Notes: {(string.IsNullOrEmpty(state.ReviewNotes) ? "(none)" : state.ReviewNotes)}
            """;

        try
        {
            AgentResponse response = await _agent.RunAsync(message);
            string content = response.Text;
            return !string.IsNullOrEmpty(content) ? content : "Draft in progress...";
        }
        catch (Exception e)
        {
            Console.WriteLine($"Author error: {e.Message}");
            return "Error generating draft. Please try again.";
        }
    }

    /// &lt;summary&gt;Author node that creates or revises draft.&lt;/summary&gt;
    public async Task&lt;ResearchState&gt; AuthorNodeAsync(ResearchState state)
    {
        Console.WriteLine("\n&gt;&gt;&gt;Author");

        string draft = await InvokeAsync(state);
        Console.WriteLine($"Draft created: {draft.Length} characters");

        state.Draft = draft;
        state.RevisionNumber += 1;
        return state;
    }
}
</code></pre>



<p>To close the circle, let&#8217;s look at the changes in Reviewer. In addition to changing to ChatClientAgent, we fix the code so that if a review fails it no longer approves, instead it requests revision (which was supposed to happen in the first place):</p>



<pre class="wp-block-code"><code>using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

namespace BlogMigration;

/// &lt;summary&gt;
/// Reviewer chain backed by a Microsoft Agent Framework &lt;see cref="ChatClientAgent"/&gt;.
///
/// MAF idiom change: the evaluation role lives in the agent's Instructions; only
/// the task + draft are sent as the per-turn user message.
///
/// Correctness fix: the previous version's &lt;c&gt;catch&lt;/c&gt; returned
/// "APPROVED - Error in review..." — meaning any transient LLM/transport failure
/// would silently approve an unreviewed draft. It now returns a revision request
/// instead, so a failed review re-loops to the author (bounded by
/// &lt;see cref="ResearchState.MaxRevisions"/&gt;) rather than shipping unchecked content.
/// &lt;/summary&gt;
public class ReviewerChain : IReviewerChain
{
    private readonly ChatClientAgent _agent;

    public ReviewerChain(IChatClient llm, ChatOptions chatOptions)
    {
        _agent = new ChatClientAgent(llm, new ChatClientAgentOptions
        {
            Name = "Reviewer",
            ChatOptions = new ChatOptions
            {
                Instructions = Prompts.ReviewerInstructions,
                Temperature = chatOptions.Temperature,
                MaxOutputTokens = chatOptions.MaxOutputTokens,
            },
        });
    }

    public async Task&lt;string&gt; InvokeAsync(ResearchState state)
    {
        string draft = state.Draft;
        int revisionNum = state.RevisionNumber;

        if (draft.Trim().Length &lt; 100)
        {
            return "APPROVED - Draft is minimal but acceptable.";
        }

        if (revisionNum &gt;= ResearchState.MaxRevisions)
        {
            return "APPROVED - Maximum revisions reached. The report is satisfactory.";
        }

        // Per-turn input only — the evaluation criteria are on the agent.
        string message = $"""
            Main Task: {state.MainTask}

            Draft to Review:
            {draft}
            """;

        try
        {
            AgentResponse response = await _agent.RunAsync(message);
            string content = response.Text;
            return !string.IsNullOrEmpty(content) ? content : "APPROVED";
        }
        catch (Exception e)
        {
            // Do NOT approve on failure — that would ship an unreviewed draft.
            // Returning feedback (not "APPROVED") routes back to the author for
            // another attempt; the revision cap still guarantees termination.
            Console.WriteLine($"Review error: {e.Message}");
            return "Review could not be completed due to a transient error. Please revise and resubmit the draft.";
        }
    }

    /// &lt;summary&gt;Node that reviews the draft.&lt;/summary&gt;
    public async Task&lt;ResearchState&gt; ReviewerNodeAsync(ResearchState state)
    {
        Console.WriteLine("\n&gt;&gt;REVIEWER");

        string review = await InvokeAsync(state);
        string preview = review.Length &gt; 100 ? review&#91;..100] : review;
        Console.WriteLine($"Review: {preview}...");

        bool isApproved = review.ToUpperInvariant().Contains("APPROVED");

        if (isApproved)
        {
            Console.WriteLine("\u2713 Draft APPROVED");
            state.ReviewNotes = "APPROVED";
            state.NextStep = "END";
        }
        else
        {
            Console.WriteLine("\u2717 Revisions needed");
            state.ReviewNotes = review;
            state.NextStep = "author";
        }

        return state;
    }
}
</code></pre>



<p>We&#8217;re ready to look at the changes to Blogger&#8230;</p>



<pre class="wp-block-code"><code>using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

namespace BlogMigration;

/// &lt;summary&gt;
/// Blogger decision chain.
///
/// MAF idiom change: the LLM fallback used to ask the model for raw JSON, then
/// manually strip ```-fences and call &lt;c&gt;JsonSerializer.Deserialize&lt;/c&gt;. That is
/// now replaced by MAF structured output — &lt;c&gt;RunAsync&amp;lt;BloggerDecision&amp;gt;&lt;/c&gt;
/// returns a typed, schema-validated &lt;see cref="BloggerDecision"/&gt; directly.
///
/// Routing note: the actual control flow is owned by the workflow edges in
/// &lt;see cref="BlogWorkflow"/&gt; (Blogger → Researcher → Author → Reviewer with a
/// bounded revision loop). The &lt;c&gt;NextStep&lt;/c&gt; this class computes is advisory;
/// its still-meaningful output is &lt;c&gt;CurrentSubTask&lt;/c&gt;, which seeds the
/// researcher. The deterministic rules below are kept because they faithfully
/// preserve the original LangGraph decision logic and avoid an LLM call in the
/// common cases.
/// &lt;/summary&gt;
public class BloggerChain : IBloggerChain
{
    // Built once and reused. Holds the static Blogger instructions; the volatile
    // state is passed per-turn as the user message.
    private readonly ChatClientAgent _agent;

    // Web-style options are sufficient: BloggerDecision carries explicit
    // &#91;JsonPropertyName] attributes (next_step / task_description) that drive the
    // generated schema regardless of naming policy.
    private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);

    public BloggerChain(IChatClient llm, ChatOptions chatOptions)
    {
        _agent = new ChatClientAgent(llm, new ChatClientAgentOptions
        {
            Name = "Blogger",
            ChatOptions = new ChatOptions
            {
                Instructions = Prompts.BloggerInstructions,
                Temperature = chatOptions.Temperature,
                MaxOutputTokens = chatOptions.MaxOutputTokens,
            },
        });
    }

    public async Task&lt;BloggerDecision&gt; InvokeAsync(ResearchState state)
    {
        List&lt;string&gt; research = state.ResearchFindings;
        string researchText = research.Count &gt; 0 ? string.Join("\n", research) : "No research yet.";
        int revision = state.RevisionNumber;
        bool hasResearch = research.Count &gt; 0;
        bool hasDraft = !string.IsNullOrWhiteSpace(state.Draft);
        string review = state.ReviewNotes;

        if (review.ToUpperInvariant().Contains("APPROVED") &amp;&amp; hasDraft)
        {
            Console.WriteLine("Blogger: Draft approved, ending workflow");
            return new BloggerDecision("END", "Report approved and complete");
        }

        if (!hasResearch)
        {
            Console.WriteLine("Blogger: No research yet, directing to researcher");
            return new BloggerDecision("researcher", $"Research the topic: {state.MainTask}");
        }

        if (hasResearch &amp;&amp; !hasDraft)
        {
            Console.WriteLine("Blogger: Have research, creating first draft");
            return new BloggerDecision("author", "Write the first draft based on research findings");
        }

        if (hasDraft &amp;&amp; string.IsNullOrEmpty(review))
        {
            Console.WriteLine("Blogger: Have draft, sending to reviewer");
            return new BloggerDecision("reviewer", "Prepare draft for review");
        }

        if (!string.IsNullOrEmpty(review) &amp;&amp; !review.ToUpperInvariant().Contains("APPROVED") &amp;&amp; revision &lt; ResearchState.MaxRevisions)
        {
            Console.WriteLine($"Blogger: Revision {revision}, sending back to author");
            return new BloggerDecision("author", "Revise the draft based on review feedback");
        }

        // Max revisions reached
        if (revision &gt;= ResearchState.MaxRevisions)
        {
            Console.WriteLine("Blogger: Max revisions reached! Ending");
            return new BloggerDecision("END", "Maximum revisions reached! Finalizing report");
        }

        // LLM decision as fallback. The dynamic state is the user message; the
        // static role lives in the agent's Instructions. MAF structured output
        // hands back a typed BloggerDecision — no fenced-block cleanup, no manual
        // JsonSerializer.Deserialize.
        string stateSummary = $"""
            Current Task: {state.MainTask}
            Research Findings: {researchText}
            Blog Draft: {(string.IsNullOrEmpty(state.Draft) ? "No draft yet." : state.Draft)}
            Reviewer Feedback: {(string.IsNullOrEmpty(review) ? "No review yet." : review)}
            Revision Number: {revision}
            """;

        try
        {
            AgentResponse&lt;BloggerDecision&gt; response =
                await _agent.RunAsync&lt;BloggerDecision&gt;(stateSummary, serializerOptions: _jsonOptions);

            BloggerDecision decision = response.Result;
            if (decision is not null &amp;&amp; !string.IsNullOrEmpty(decision.NextStep))
            {
                return decision;
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"LLM decision error: {e.Message}");
        }

        // Final fallback - continue with author
        Console.WriteLine("Blogger: Using final fallback - continuing with author");
        return new BloggerDecision("author", "Continue with draft creation");
    }

    /// &lt;summary&gt;Blogger decides the next step.&lt;/summary&gt;
    public async Task&lt;ResearchState&gt; BloggerNodeAsync(ResearchState state)
    {
        Console.WriteLine("\n&gt;&gt;&gt;Blogger");

        BloggerDecision decision = await InvokeAsync(state);

        string nextStep = string.IsNullOrEmpty(decision.NextStep) ? "researcher" : decision.NextStep;
        string taskDesc = string.IsNullOrEmpty(decision.TaskDescription) ? "Continue work" : decision.TaskDescription;

        Console.WriteLine($"Decision: {nextStep}");
        Console.WriteLine($"Task: {taskDesc}");

        state.NextStep = nextStep;
        state.CurrentSubTask = taskDesc;
        return state;
    }
}
</code></pre>



<p>We&#8217;re down to the BlogWorkflow and Program.cs. Let&#8217;s start with the former. BlogWorkflow switches to RunStreamingAsync and WatchStreamAsync to stream the executor lifecycle events, while maintaining the topology:</p>



<pre class="wp-block-code"><code>using Microsoft.Agents.AI.Workflows;

namespace BlogMigration;

/// &lt;summary&gt;
/// Blog creation workflow built on the Microsoft Agent Framework workflow engine.
///
/// Topology (faithful to the original LangGraph StateGraph):
///   Blogger → Researcher → Author → Reviewer
///   Reviewer ⇄ Author  (bounded revision loop)
///   Reviewer → Output  (on approval or revision cap)
///
/// The revision loop is bounded by &lt;see cref="ResearchState.MaxRevisions"/&gt;: the
/// loop-back edge only fires while the draft is unapproved AND the revision count
/// is below the cap, so the workflow is guaranteed to terminate even if the
/// reviewer never returns "APPROVED".
/// &lt;/summary&gt;
public class BlogWorkflow(
    IBloggerChain blogger,
    IResearcherAgent researcher,
    IAuthorChain author,
    IReviewerChain reviewer) : IBlogWorkflow
{
    public async Task&lt;ResearchState&gt; RunAsync(ResearchState state)
    {
        var bloggerExecutor = new BloggerExecutor(blogger);
        var researcherExecutor = new ResearcherExecutor(researcher);
        var authorExecutor = new AuthorExecutor(author);
        var reviewerExecutor = new ReviewerExecutor(reviewer);

        Workflow workflow = new WorkflowBuilder(bloggerExecutor)
            .AddEdge(bloggerExecutor, researcherExecutor)
            .AddEdge(researcherExecutor, authorExecutor)
            .AddEdge(authorExecutor, reviewerExecutor)
            // Bounded revision loop: route back to the author only while the draft
            // still needs work and the revision cap has not been reached. When the
            // condition is false the reviewer instead yields the final output.
            .AddEdge&lt;ResearchState&gt;(reviewerExecutor, authorExecutor, condition: s =&gt; s?.NeedsRevision == true)
            .WithOutputFrom(reviewerExecutor)
            .Build();

        // Stream execution instead of running to completion in one shot. The
        // topology is identical to before (proven terminating, MAF-Doctor grade A);
        // streaming simply surfaces each executor's lifecycle as it happens, giving
        // live progress and replacing the scattered Console.WriteLine tracing that
        // previously lived inside the node classes. The final ResearchState is
        // captured from the WorkflowOutputEvent emitted by the reviewer.
        StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, state);

        ResearchState? result = null;

        await foreach (WorkflowEvent evt in run.WatchStreamAsync())
        {
            switch (evt)
            {
                case ExecutorInvokedEvent invoked:
                    Console.WriteLine($"&#91;workflow] → {invoked.ExecutorId} started");
                    break;

                case ExecutorCompletedEvent completed:
                    Console.WriteLine($"&#91;workflow] ✓ {completed.ExecutorId} completed");
                    break;

                case ExecutorFailedEvent failed:
                    Console.WriteLine($"&#91;workflow] ✗ {failed.ExecutorId} failed: {(failed.Data as Exception)?.Message}");
                    break;

                case WorkflowOutputEvent { Data: ResearchState finalState }:
                    // The reviewer yielded the final, approved (or revision-capped) state.
                    result = finalState;
                    break;
            }
        }

        // Fall back to the input state only if no output event was ever produced.
        return result ?? state;
    }
}
</code></pre>



<p>Finally, we&#8217;re ready to update Program.cs. Here we add UseFunctionInvocation to the IChatClient pipeline which is required for the researcher&#8217;s tool calls to execute:</p>



<pre class="wp-block-code"><code>using System.ClientModel;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using BlogMigration;
using Microsoft.Extensions.AI;
using OpenAI;

const string fileName = "config.json";

using var stream = File.OpenRead(fileName);
using var document = JsonDocument.Parse(stream);
JsonElement config = document.RootElement;

string? GetValue(string key) =&gt;
    config.TryGetProperty(key, out JsonElement value) ? value.GetString() : null;

Environment.SetEnvironmentVariable("OPENAI_API_KEY", GetValue("API_KEY"));
Environment.SetEnvironmentVariable("OPENAI_BASE_URL", GetValue("OPENAI_API_BASE"));
Environment.SetEnvironmentVariable("TAVILY_API_KEY", GetValue("TAVILY_API_KEY"));

string modelName = "gpt-4o-mini";

var openAIClient = new OpenAIClient(
    new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!),
    new OpenAIClientOptions
    {
        Endpoint = new Uri(Environment.GetEnvironmentVariable("OPENAI_BASE_URL")!)
    });

// Build the IChatClient pipeline once and share it across all agents.
// UseFunctionInvocation() adds the middleware that actually *executes* the tool
// calls the model requests — without it, attaching the Tavily tool to the
// Researcher agent would let the model ask for a search but nothing would run it.
// Middleware is applied inner-to-outer, so function invocation wraps the raw
// OpenAI client. (To add distributed tracing later, chain .UseOpenTelemetry()
// here and register the source with a TracerProvider.)
IChatClient llm = openAIClient
    .GetChatClient(modelName)
    .AsIChatClient()
    .AsBuilder()
    .UseFunctionInvocation()
    .Build();

var chatOptions = new ChatOptions
{
    Temperature = 0,
    MaxOutputTokens = 4096
};

var tavilyHttpClient = new HttpClient { BaseAddress = new Uri("https://api.tavily.com/") };
tavilyHttpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TAVILY_API_KEY"));

AIFunction tavilyTool = AIFunctionFactory.Create(
    async (string query) =&gt;
    {
        var request = new
        {
            query,
            max_results = 5,
            topic = "general",
            include_answer = false,
            include_raw_content = false,
            search_depth = "basic"
        };

        using HttpResponseMessage response = await tavilyHttpClient.PostAsJsonAsync("search", request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    },
    name: "tavily_search",
    description: "A search engine optimized for comprehensive, accurate, and trusted results.");

// Creating a callable object
var bloggerChain = new BloggerChain(llm, chatOptions);
var researcherAgent = new ResearcherAgent(llm, chatOptions, tavilyTool);
var authorChain = new AuthorChain(llm, chatOptions);
var reviewerChain = new ReviewerChain(llm, chatOptions);
var app = new BlogWorkflow(bloggerChain, researcherAgent, authorChain, reviewerChain);

// Run the workflow for a sample topic
var initialState = new ResearchState
{
    MainTask = "use of multiagents in writing a C# application"
};

ResearchState result = await app.RunAsync(initialState);

Console.WriteLine("\n========== RESULTS ==========");
Console.WriteLine($"Task: {result.MainTask}");

Console.WriteLine($"\nResearch Findings ({result.ResearchFindings.Count}):");
foreach (string finding in result.ResearchFindings)
{
    Console.WriteLine($"- {finding}");
}

Console.WriteLine($"\nDraft:\n{result.Draft}");
Console.WriteLine($"\nReview Notes: {result.ReviewNotes}");
Console.WriteLine($"Revision Number: {result.RevisionNumber}");
Console.WriteLine("=============================");
</code></pre>



<p>I&#8217;ll have a lot more to say about the Microsoft Agent Framework in coming blog posts, often pointing back to this code.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Dependency Injection &amp; Agent Framework</title>
		<link>https://jesseliberty.com/2026/06/26/dependency-injection-agent-framework/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Fri, 26 Jun 2026 13:33:30 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Essentials]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13373</guid>

					<description><![CDATA[In the previous blog posts we ported a Python implementation of an agentic application to C# and Microsoft Agent Framework. We used interfaces, but we did not use Dependency Injection (DI). It is pretty easy to add. Agents, tools, executors &#8230; <a href="https://jesseliberty.com/2026/06/26/dependency-injection-agent-framework/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<p>In the previous blog posts we ported a Python implementation of an agentic application to C# and Microsoft Agent Framework. We used interfaces, but we did not use Dependency Injection (DI). It is pretty easy to add.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="276" height="187" src="https://jesseliberty.com/wp-content/uploads/2026/06/dicartoon.jpg" alt="" class="wp-image-13375" srcset="https://jesseliberty.com/wp-content/uploads/2026/06/dicartoon.jpg 276w, https://jesseliberty.com/wp-content/uploads/2026/06/dicartoon-150x102.jpg 150w" sizes="auto, (max-width: 276px) 100vw, 276px" /></figure>



<p>Agents, tools, executors and workflows all depend on interfaces, and DI depends on registering the relationship between these interfaces and the concrete class that implements them.</p>



<p> For example, here is how you create a workflow</p>



<pre class="wp-block-code"><code>services.AddSingleton&lt;Workflow>(sp =>
{
    var blogger = sp.GetRequiredService&lt;BloggerExecutor>();
    var researcher = sp.GetRequiredService&lt;ResearcherExecutor>();
    var author = sp.GetRequiredService&lt;AuthorExecutor>();
    var reviewer = sp.GetRequiredService&lt;ReviewerExecutor>();

    return new WorkflowBuilder(blogger)
        .AddEdge(blogger, researcher)
        .AddEdge(researcher, author)
        .AddEdge(author, reviewer)
        .AddEdge&lt;ResearchState>(reviewer, author, s => s?.NeedsRevision == true)
        .WithOutputFrom(reviewer)
        .Build();
});
</code></pre>



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



<p>In our console application we would then register the interfaces, agents, executors, and workflows in .configureServices:</p>



<pre class="wp-block-code"><code>using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        // Register your interfaces here
        services.AddSingleton&lt;IResearchService, ResearchService>();
        services.AddSingleton&lt;IAuthorTools, AuthorTools>();
        services.AddSingleton&lt;IReviewerLogic, ReviewerLogic>();

        // Register agents
        services.AddSingleton&lt;ResearchAgent>();
        services.AddSingleton&lt;AuthorAgent>();
        services.AddSingleton&lt;BloggerAgent>();
        services.AddSingleton&lt;ReviewerAgent>();

        // Register executors
        services.AddSingleton&lt;ResearcherExecutor>();
        services.AddSingleton&lt;AuthorExecutor>();
        services.AddSingleton&lt;BloggerExecutor>();
        services.AddSingleton&lt;ReviewerExecutor>();

        // Register workflow
        services.AddSingleton&lt;Workflow>(sp =>
        {
            var blogger = sp.GetRequiredService&lt;BloggerExecutor>();
            var researcher = sp.GetRequiredService&lt;ResearcherExecutor>();
            var author = sp.GetRequiredService&lt;AuthorExecutor>();
            var reviewer = sp.GetRequiredService&lt;ReviewerExecutor>();

            return new WorkflowBuilder(blogger)
                .AddEdge(blogger, researcher)
                .AddEdge(researcher, author)
                .AddEdge(author, reviewer)
                .AddEdge&lt;ResearchState>(reviewer, author, s => s?.NeedsRevision == true)
                .WithOutputFrom(reviewer)
                .Build();
        });
    })
    .Build();

// Resolve your entry point service
var workflow = host.Services.GetRequiredService&lt;Workflow>();
</code></pre>



<p>This has all the advantages of DI as used anywhere, principally decoupling objects from their dependencies. This allows for greater flexibility (e.g., swapping out executors, etc.) and especially for testing (where moq objects can be used).</p>



<p>DI will inject all the following relationships:</p>



<ul class="wp-block-list">
<li>Agents depend on services</li>



<li>Tools depend on services</li>



<li>Executors depend on agents</li>



<li>Workflows depend on executors</li>
</ul>



<p>And your console app resolves the workflow.</p>



<p>In an ASP application you do the same, but in the usual place (startup.cs for ASP.NET Core and in program.cs for Minimal API)</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Migrating Agentic Code Python -&gt; C# Part 6 (final)</title>
		<link>https://jesseliberty.com/2026/06/22/migrating-agentic-code-python-c-part-6-final/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Mon, 22 Jun 2026 21:59:50 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13350</guid>

					<description><![CDATA[Note, the complete source code for the .NET version of this demo application is now available at https://github.com/JesseLiberty/blogMigration&#8212;public In the previous post we finished up creating our agents. You&#8217;ll remember that each of the agents declared nodes. We&#8217;re finally going &#8230; <a href="https://jesseliberty.com/2026/06/22/migrating-agentic-code-python-c-part-6-final/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note, the complete source code for the .NET version of this demo application is now available at <a href="https://github.com/JesseLiberty/blogMigration---public">https://github.com/JesseLiberty/blogMigration&#8212;public</a></p>
</blockquote>



<p>In the <a href="https://jesseliberty.com/2026/06/22/migrating-agentic-code-python-c-part-5/">previous post</a> we finished up creating our agents. You&#8217;ll remember that each of the agents declared nodes. We&#8217;re finally going to put them to use in a class <em>BlogWorkflow</em>. However, up to now we&#8217;ve not fully taken advantage of the Microsoft Agent Framework (MAF). Let&#8217;s fix that up first. To do so we&#8217;ll add a class BlogExecutors. We&#8217;ll create MAF workflow executors that will wrap existing &#8220;node&#8221; chains so that the business logic is reused unchanged. </p>



<p><strong>Note: In this post we&#8217;re only going to move <em>towards</em> Microsoft Agent Framework from where things currently stand. To see this fully migrated to an application that truly utilizes MAF, navigate to </strong><a href="https://jesseliberty.com/2026/06/28/migrating-c-microsoft-agent-framework/">here</a></p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="790" height="800" src="https://jesseliberty.com/wp-content/uploads/2026/06/iron-work-790x800.jpg" alt="" class="wp-image-13354" style="aspect-ratio:0.9875178480954355;width:303px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/06/iron-work-790x800.jpg 790w, https://jesseliberty.com/wp-content/uploads/2026/06/iron-work-296x300.jpg 296w, https://jesseliberty.com/wp-content/uploads/2026/06/iron-work-148x150.jpg 148w, https://jesseliberty.com/wp-content/uploads/2026/06/iron-work-768x777.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/06/iron-work.jpg 905w" sizes="auto, (max-width: 790px) 100vw, 790px" /></figure>



<p><br />Let&#8217;s start with the BloggerExecutor which will allow the blogger to plan the task and seed the sub-task:</p>



<pre class="wp-block-code"><code>using Microsoft.Agents.AI.Workflows;

namespace BlogMigration;

/// &lt;summary&gt;Entry executor: lets the blogger plan the task and seed the sub-task.&lt;/summary&gt;
internal sealed partial class BloggerExecutor(IBloggerChain blogger) : Executor("Blogger")
{
    &#91;MessageHandler]
    private async ValueTask&lt;ResearchState&gt; HandleAsync(ResearchState state, IWorkflowContext context)
        =&gt; await blogger.BloggerNodeAsync(state);
}</code></pre>



<p>An executor is a node in Microsoft Agent Framework. Each executor can receive messages, process them and emit new messages. In this case, whenever the workflow engine routes a ResearchState message to the executor, this method is invoked. The method takes the current workflow state and the workflow context and returns the updated ResearchState.</p>



<p>The executor is just a thin wrapper around the Blogger agent. The handler calls the Blogger node and the node updates the state.</p>



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



<p>Let&#8217;s go ahead and create the other Executors</p>



<pre class="wp-block-code"><code>/// &lt;summary&gt;Gathers research findings.&lt;/summary&gt;
internal sealed partial class ResearcherExecutor(IResearcherAgent researcher) : Executor("Researcher")
{
    &#91;MessageHandler]
    private async ValueTask&lt;ResearchState&gt; HandleAsync(ResearchState state, IWorkflowContext context)
        =&gt; await researcher.ResearchNodeAsync(state);
}

/// &lt;summary&gt;Writes or revises the draft (increments the revision counter).&lt;/summary&gt;
internal sealed partial class AuthorExecutor(IAuthorChain author) : Executor("Author")
{
    &#91;MessageHandler]
    private async ValueTask&lt;ResearchState&gt; HandleAsync(ResearchState state, IWorkflowContext context)
        =&gt; await author.AuthorNodeAsync(state);
}

/// &lt;summary&gt;
/// Reviews the draft and records approval / revision notes. Acts as the terminal
/// output node: when no further revision is needed it yields the final state.
/// &lt;/summary&gt;
internal sealed partial class ReviewerExecutor(IReviewerChain reviewer) : Executor("Reviewer")
{
    &#91;MessageHandler]
    private async ValueTask&lt;ResearchState&gt; HandleAsync(ResearchState state, IWorkflowContext context)
    {
        state = await reviewer.ReviewerNodeAsync(state);

        if (!state.NeedsRevision)
        {
            // Approved, or the revision cap was hit — emit the final result.
            await context.YieldOutputAsync(state);
        }

        // Returned state is routed back to the author only when the loop edge
        // condition (NeedsRevision) is satisfied; otherwise it goes nowhere.
        return state;
    }</code></pre>



<p>With that in place we can build the workflow.  We begin by creating the executors as wrappers around our agents,</p>



<pre class="wp-block-code"><code>/// &lt;summary&gt;
///   Blogger → Researcher → Author → Reviewer
///   Reviewer ⇄ Author  (bounded revision loop)
///   Reviewer → Output  (on approval or revision cap)
///
/// The revision loop is bounded by &lt;see cref="ResearchState.MaxRevisions"/&gt;: the
/// loop-back edge only fires while the draft is unapproved AND the revision count
/// is below the cap, so the workflow is guaranteed to terminate even if the
/// reviewer never returns "APPROVED".
/// &lt;/summary&gt;

public class BlogWorkflow(
    IBloggerChain blogger,
    IResearcherAgent researcher,
    IAuthorChain author,
    IReviewerChain reviewer) : IBlogWorkflow
{
    public async Task&lt;ResearchState&gt; RunAsync(ResearchState state)
    {
        var bloggerExecutor = new BloggerExecutor(blogger);
        var researcherExecutor = new ResearcherExecutor(researcher);
        var authorExecutor = new AuthorExecutor(author);
        var reviewerExecutor = new ReviewerExecutor(reviewer);</code></pre>



<p>Now we create a Workflow object and add edges between our nodes</p>



<pre class="wp-block-code"><code> Workflow workflow = new WorkflowBuilder(bloggerExecutor)
            .AddEdge(bloggerExecutor, researcherExecutor)
            .AddEdge(researcherExecutor, authorExecutor)
            .AddEdge(authorExecutor, reviewerExecutor)
            .AddEdge&lt;ResearchState&gt;(reviewerExecutor, authorExecutor, condition: s =&gt; s?.NeedsRevision == true)
            .WithOutputFrom(reviewerExecutor)
            .Build();</code></pre>



<p>Notice the conditional edge. This final AddEdge says &#8220;if the condition is not null and the draft needs revision (remember, the Reviewer caps the number of revisions at 4) then follow this edge. If NeedsRevision equals false, this edge is ignored.</p>



<p>We&#8217;re ready to let &#8216;er rip</p>



<pre class="wp-block-code"><code>     Run run = await InProcessExecution.RunAsync(workflow, state);

        foreach (WorkflowEvent evt in run.NewEvents)
        {
            if (evt is WorkflowOutputEvent { Data: ResearchState result })
            {
                return result;
            }
        }

        return state;
    }</code></pre>



<p>All that&#8217;s left is to create Program.cs. We&#8217;ll begin by initializing the environment we need</p>



<pre class="wp-block-code"><code>using System.ClientModel;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using BlogMigration;
using Microsoft.Extensions.AI;
using OpenAI;

const string fileName = "config.json";

using var stream = File.OpenRead(fileName);
using var document = JsonDocument.Parse(stream);
JsonElement config = document.RootElement;

string? GetValue(string key) =&gt;
    config.TryGetProperty(key, out JsonElement value) ? value.GetString() : null;

Environment.SetEnvironmentVariable("OPENAI_API_KEY", GetValue("API_KEY"));
Environment.SetEnvironmentVariable("OPENAI_BASE_URL", GetValue("OPENAI_API_BASE"));
Environment.SetEnvironmentVariable("TAVILY_API_KEY", GetValue("TAVILY_API_KEY"));

string modelName = "gpt-4o-mini";

var openAIClient = new OpenAIClient(
    new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!),
    new OpenAIClientOptions
    {
        Endpoint = new Uri(Environment.GetEnvironmentVariable("OPENAI_BASE_URL")!)
    });</code></pre>



<p>Now let&#8217;s instantiate our IChatClient (the LLM) and set the options. We&#8217;ll set temperature to 0 to have minimum variability and we&#8217;ll set the maximum output tokens. </p>



<pre class="wp-block-code"><code>IChatClient llm = openAIClient.GetChatClient(modelName).AsIChatClient();

var chatOptions = new ChatOptions
{
    Temperature = 0,
    MaxOutputTokens = 4096
};</code></pre>



<p>Tavily is the library we&#8217;ll use for searching the web. You can learn more about it at https://tavily.com. </p>



<pre class="wp-block-code"><code>var tavilyHttpClient = new HttpClient { BaseAddress = new Uri("https://api.tavily.com/") };
tavilyHttpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TAVILY_API_KEY"));

AIFunction tavilyTool = AIFunctionFactory.Create(
    async (string query) =&gt;
    {
        var request = new
        {
            query,
            max_results = 5,
            topic = "general",
            include_answer = false,
            include_raw_content = false,
            search_depth = "basic"
        };

        using HttpResponseMessage response = await tavilyHttpClient.PostAsJsonAsync("search", request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    },
    name: "tavily_search",
    description: "A search engine optimized for comprehensive, accurate, and trusted results.");</code></pre>



<p>Let&#8217;s set up all the agents and the BlogWorkflow</p>



<pre class="wp-block-code"><code>var bloggerChain = new BloggerChain(llm, chatOptions);
var researcherAgent = new ResearcherAgent(llm, chatOptions, tavilyTool);
var authorChain = new AuthorChain(llm, chatOptions);
var reviewerChain = new ReviewerChain(llm, chatOptions);

var app = new BlogWorkflow(bloggerChain, researcherAgent, authorChain, reviewerChain);</code></pre>



<p>We&#8217;ll run the workflow for a sample topic</p>



<pre class="wp-block-code"><code>var initialState = new ResearchState
{
    MainTask = "use of multiagents in writing a C# application"
};

ResearchState result = await app.RunAsync(initialState);

Console.WriteLine("\n========== RESULTS ==========");
Console.WriteLine($"Task: {result.MainTask}");

Console.WriteLine($"\nResearch Findings ({result.ResearchFindings.Count}):");
foreach (string finding in result.ResearchFindings)
{
    Console.WriteLine($"- {finding}");
}

Console.WriteLine($"\nDraft:\n{result.Draft}");
Console.WriteLine($"\nReview Notes: {result.ReviewNotes}");
Console.WriteLine($"Revision Number: {result.RevisionNumber}");
Console.WriteLine("=============================");</code></pre>



<p>Before we run this, here&#8217;s a look at the csproj:</p>



<pre class="wp-block-code"><code>&lt;Project Sdk="Microsoft.NET.Sdk"&gt;

  &lt;PropertyGroup&gt;
    &lt;OutputType&gt;Exe&lt;/OutputType&gt;
    &lt;TargetFramework&gt;net10.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="Microsoft.Agents.AI.Workflows" Version="1.10.0" /&gt;
    &lt;PackageReference Include="Microsoft.Agents.AI.Workflows.Generators" Version="1.10.0"&gt;
      &lt;IncludeAssets&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;/IncludeAssets&gt;
      &lt;PrivateAssets&gt;all&lt;/PrivateAssets&gt;
    &lt;/PackageReference&gt;
    &lt;PackageReference Include="Microsoft.Extensions.AI" Version="10.7.0" /&gt;
    &lt;PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.7.0" /&gt;
    &lt;PackageReference Include="OpenAI" Version="2.11.0" /&gt;
  &lt;/ItemGroup&gt;

&lt;/Project&gt;
</code></pre>



<p>That&#8217;s it. Easy Peasy.</p>



<p>Here is the output of running the above&#8230;</p>



<pre class="wp-block-code"><code>&gt;&gt;&gt;Blogger
Blogger: No research yet, directing to researcher
Decision: researcher
Task: Research the topic: use of multiagents in writing a C# application

&gt;&gt;&gt;RESEARCHER
Researching: Research the topic: use of multiagents in writing a C# application
Found: The search results highlight various resources and insights on developing multi-agent applications i...

&gt;&gt;&gt;Author
Draft created: 3140 characters

&gt;&gt;REVIEWER
Review: APPROVED - The draft effectively introduces the concept of multi-agent systems in C# and provides va...
✓ Draft APPROVED

========== RESULTS ==========
Task: use of multiagents in writing a C# application

Research Findings (1):
- The search results highlight various resources and insights on developing multi-agent applications in C#.

1. **Creating a Multi-Agent Application**: Jesse Liberty's blog post introduces the concept of a multi-agent application, showcasing a test run of an application designed to generate blog posts. The post promises a detailed breakdown of the application in subsequent entries, indicating a focus on practical implementation.

2. **Semantic Kernel Framework**: A YouTube video discusses building a multi-agent application using the Semantic Kernel framework in C#. This resource likely provides a hands-on approach to leveraging this framework for multi-agent systems, emphasizing its capabilities and features.

3. **Microsoft Agent Framework**: Another YouTube tutorial presents the Microsoft Agent Framework as a robust alternative for creating multi-agent architectures in .NET. This resource suggests that the framework offers significant tools and functionalities for developers looking to implement multi-agent systems.

Overall, these findings suggest a growing interest in multi-agent systems within the C# ecosystem, with various frameworks and tutorials available to assist developers in creating sophisticated applications.

Draft:
# Harnessing Multi-Agent Systems in C#: A Comprehensive Guide

In the evolving landscape of software development, multi-agent systems (MAS) have emerged as a powerful paradigm, particularly in the realm of C#. These systems consist of multiple interacting agents, each capable of autonomous decision-making, which can lead to more efficient and scalable applications. This post explores the key resources and frameworks available for developing multi-agent applications in C#, providing a roadmap for developers interested in this innovative approach.

## Understanding Multi-Agent Applications

Multi-agent applications are designed to solve complex problems by distributing tasks among various agents. Each agent operates independently, yet they can collaborate to achieve common goals. Jesse Liberty's blog post serves as an excellent introduction to this concept, showcasing a practical example of a multi-agent application that generates blog posts. Liberty promises a detailed breakdown of the application in future entries, making it a valuable resource for developers looking to implement similar functionalities.

## Leveraging the Semantic Kernel Framework

For those seeking a hands-on approach, the Semantic Kernel framework offers a robust environment for building multi-agent applications in C#. A recent YouTube video delves into the capabilities of this framework, highlighting its features that facilitate the development of intelligent agents. The Semantic Kernel is particularly beneficial for developers aiming to integrate natural language processing and machine learning into their applications, allowing agents to understand and respond to user inputs more effectively.

## Exploring the Microsoft Agent Framework

Another noteworthy resource is the Microsoft Agent Framework, which provides a comprehensive set of tools for creating multi-agent architectures within the .NET ecosystem. A tutorial available on YouTube outlines the framework's functionalities, demonstrating how it can be utilized to develop sophisticated multi-agent systems. This framework is particularly advantageous for developers who are already familiar with the Microsoft stack, as it seamlessly integrates with existing .NET applications.

## Conclusion

The interest in multi-agent systems within the C# ecosystem is on the rise, driven by the need for more intelligent and responsive applications. With resources like Jesse Liberty's blog, the Semantic Kernel framework, and the Microsoft Agent Framework, developers have access to a wealth of knowledge and tools to create innovative multi-agent applications. As you embark on your journey into multi-agent development, these resources will serve as invaluable guides, helping you harness the full potential of this exciting technology.

By embracing multi-agent systems, you can elevate your applications, making them more dynamic and capable of handling complex tasks with ease. Whether you're generating content, automating processes, or enhancing user interactions, the possibilities are endless. Start exploring today and unlock the future of intelligent software development in C#.

Review Notes: APPROVED
Revision Number: 1</code></pre>



<p><em>Note, I did not tell it to use my blog or what to say about it.</em> </p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>.NET Live Turns the Tables</title>
		<link>https://jesseliberty.com/2026/06/22/net-live-turns-the-tables/</link>
		
		<dc:creator><![CDATA[Jesse Liberty]]></dc:creator>
		<pubDate>Mon, 22 Jun 2026 17:11:31 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://jesseliberty.com/?p=13347</guid>

					<description><![CDATA[I had the great privilege of being interviewed about AI on .NET Live. You can find the video on YouTube. Find my YouTube channel here.]]></description>
										<content:encoded><![CDATA[
<p>I had the great privilege of being interviewed about AI on .NET Live. You can find the video on <a href="https://www.youtube.com/watch?v=LzHo8StEJrg">YouTube</a>.</p>



<figure class="wp-block-image size-large is-resized"><a href="https://www.youtube.com/watch?v=LzHo8StEJrg"><img loading="lazy" decoding="async" width="800" height="450" src="https://jesseliberty.com/wp-content/uploads/2026/06/Dotnet-live-800x450.jpg" alt="" class="wp-image-13348" style="width:464px;height:auto" srcset="https://jesseliberty.com/wp-content/uploads/2026/06/Dotnet-live-800x450.jpg 800w, https://jesseliberty.com/wp-content/uploads/2026/06/Dotnet-live-300x169.jpg 300w, https://jesseliberty.com/wp-content/uploads/2026/06/Dotnet-live-150x84.jpg 150w, https://jesseliberty.com/wp-content/uploads/2026/06/Dotnet-live-768x432.jpg 768w, https://jesseliberty.com/wp-content/uploads/2026/06/Dotnet-live.jpg 894w" sizes="auto, (max-width: 800px) 100vw, 800px" /></a></figure>



<p>Find <em>my </em>YouTube channel <a href="https://youtube.com/jesseliberty">here</a>.</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>