<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Venya Brodetskiy</title>
    <description>The latest articles on DEV Community by Venya Brodetskiy (@venyabrodetskiy).</description>
    <link>https://dev.to/venyabrodetskiy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1174239%2Fb3a32727-c22b-42e8-bf83-36808a1c1775.jpeg</url>
      <title>DEV Community: Venya Brodetskiy</title>
      <link>https://dev.to/venyabrodetskiy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/venyabrodetskiy"/>
    <language>en</language>
    <item>
      <title>Introducing design-lens-workshop: A Technical Design Skill for Complex Features</title>
      <dc:creator>Venya Brodetskiy</dc:creator>
      <pubDate>Sun, 02 Aug 2026 08:51:43 +0000</pubDate>
      <link>https://dev.to/venyabrodetskiy/introducing-design-lens-workshop-a-standalone-technical-design-skill-inspired-by-specrew-51k1</link>
      <guid>https://dev.to/venyabrodetskiy/introducing-design-lens-workshop-a-standalone-technical-design-skill-inspired-by-specrew-51k1</guid>
      <description>&lt;p&gt;A technical design workshop brings people with different responsibilities into the same conversation. Together, they look at a feature from several angles, challenge assumptions, and turn an idea into something the team can actually build.&lt;/p&gt;

&lt;p&gt;When developers work with a team of AI agents, that conversation does not happen automatically. Agents can move from a feature request to an implementation plan before the key questions about architecture, data, UX, security, or operations have even been asked.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;design-lens-workshop&lt;/code&gt; adds that missing step as a structured conversation with an AI agent. It builds a compact design brief, selects the perspectives most likely to affect the solution, and works through the key trade-offs and decisions with you before producing a design the team can review. It is meant for new products, subsystems, and larger or uncertain features — cases where finding gaps early is worth the extra effort.&lt;/p&gt;

&lt;p&gt;The skill grew out of one of my favorite parts of &lt;a href="https://github.com/alonf/specrew" rel="noopener noreferrer"&gt;Specrew&lt;/a&gt;: its Design Workshop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Specrew?
&lt;/h2&gt;

&lt;p&gt;Specrew was created by &lt;a href="https://medium.com/@alonfliess" rel="noopener noreferrer"&gt;Alon Fliess&lt;/a&gt;. It is a governed workflow for AI-assisted software development.&lt;/p&gt;

&lt;p&gt;Instead of treating an AI coding agent as a chatbot that waits for the next prompt, Specrew drives the software development lifecycle. It helps move work through discovery, specification, design, planning, implementation, review, and closeout. The agents do the work between boundaries, while the human remains responsible for the important decisions.&lt;/p&gt;

&lt;p&gt;Alon explains the motivation and the complete approach in &lt;a href="https://medium.com/@alonfliess/the-ai-sdlc-from-vibe-coding-to-governed-agentic-development-a726476184b1" rel="noopener noreferrer"&gt;The AI SDLC — From Vibe Coding to Governed Agentic Development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Specrew is a complete methodology and runtime. But while looking through it, I started thinking that some of its ideas could also be useful independently. I especially liked the Design Workshop — it quickly became one of my favorite parts of Specrew. It felt valuable enough to stand on its own, even without adopting the complete Specrew workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that interested me most
&lt;/h2&gt;

&lt;p&gt;The Design Workshop sits between understanding what we want to build and deciding how to implement it.&lt;/p&gt;

&lt;p&gt;Instead of asking one agent for a finished architecture, it examines the feature through several design lenses. Each lens brings a different perspective and asks the questions a specialist in that area would normally ask.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the system boundaries and component responsibilities?&lt;/li&gt;
&lt;li&gt;Which requirements and non-functional qualities actually drive the design?&lt;/li&gt;
&lt;li&gt;Who owns the data, and what consistency guarantees do we need?&lt;/li&gt;
&lt;li&gt;How do integrations fail, retry, and evolve?&lt;/li&gt;
&lt;li&gt;What are the trust boundaries?&lt;/li&gt;
&lt;li&gt;How will we deploy, observe, and recover the feature?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is very close to how I approach design work as a tech lead. I do not only want an answer. I want to explore the problem from several relevant directions, understand the trade-offs, and make the decisions visible before they turn into code.&lt;/p&gt;

&lt;p&gt;I wondered: why not make this workshop available as a standalone skill?&lt;/p&gt;

&lt;h2&gt;
  
  
  A standalone Design Lens Workshop
&lt;/h2&gt;

&lt;p&gt;I adapted the Specrew Design Workshop into &lt;a href="https://github.com/Zio-Net/skills/tree/main/skills/design-lens-workshop" rel="noopener noreferrer"&gt;&lt;code&gt;design-lens-workshop&lt;/code&gt;&lt;/a&gt;, a reusable Agent Skill that can be installed and invoked independently.&lt;/p&gt;

&lt;p&gt;The skill retains the lens-driven facilitation and co-design method, while leaving out the surrounding Specrew lifecycle and runtime. It does not manage feature stages, enforce gates, generate an implementation plan, or continue into coding. It runs a technical design workshop and ends with a reviewable design handoff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu05rua1jqatkdgmkkano.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu05rua1jqatkdgmkkano.png" alt="Specrew places the Design Workshop inside a complete governed lifecycle, while the standalone skill makes the workshop independently invokable and stops at a design handoff." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The workshop remains part of Specrew's full lifecycle, but it can also be useful as an independently invoked skill.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The standalone version keeps the same set of nine technical lenses as the original Specrew Design Workshop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture core;&lt;/li&gt;
&lt;li&gt;component design;&lt;/li&gt;
&lt;li&gt;requirements and non-functional requirements;&lt;/li&gt;
&lt;li&gt;UI and UX;&lt;/li&gt;
&lt;li&gt;data and storage;&lt;/li&gt;
&lt;li&gt;security and compliance;&lt;/li&gt;
&lt;li&gt;integrations and APIs;&lt;/li&gt;
&lt;li&gt;DevOps and operations;&lt;/li&gt;
&lt;li&gt;observability and resilience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The skill does not explore all nine lenses at the same depth. It always evaluates architecture, component design, and requirements/NFRs as foundations. From the remaining six, the agent selects those that can materially shape the design, assigns each selected lens full, medium, or light depth, and explains the skipped lenses. A light foundation may simply establish that it introduces no new concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the skill works
&lt;/h2&gt;

&lt;p&gt;The default is &lt;strong&gt;Workshop mode&lt;/strong&gt;. Its flow is intentionally simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent builds a small design brief from the conversation, supplied documents, and relevant repository evidence. Important statements are marked as known, assumed, or open.&lt;/li&gt;
&lt;li&gt;It proposes the workshop agenda and waits for confirmation or adjustment.&lt;/li&gt;
&lt;li&gt;It works through one selected lens at a time.&lt;/li&gt;
&lt;li&gt;For each lens, it presents the current understanding and evidence before asking only for input that can materially change the design. A light lens with no new concern may need only a quick &lt;code&gt;confirm / correct / move on&lt;/code&gt; review.&lt;/li&gt;
&lt;li&gt;It records the decision, rationale, consequences, and whether the direction was recommended, confirmed, delegated, provisional, skipped, or left open.&lt;/li&gt;
&lt;li&gt;It reconciles the decisions across lenses and returns a design handoff.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnyzszpz4vcmd4evn0qxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnyzszpz4vcmd4evn0qxl.png" alt="Workshop mode evaluates nine technical lenses, builds a right-sized agenda, and explores the selected lenses one at a time before producing a design handoff." width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workshop mode right-sizes the agenda, then explores each selected lens with the human.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Workshop mode is deliberately thorough. The skill also offers &lt;strong&gt;Parallel scan mode&lt;/strong&gt;, but only when I request it explicitly. It scans every selected lens independently and, when the host supports delegation, runs those scans in parallel with separate subagents.&lt;/p&gt;

&lt;p&gt;The coordinator consolidates overlapping findings into cross-lens concern clusters and a prioritized question agenda, then stops for discussion. Any concern cluster can then continue in Workshop mode.&lt;/p&gt;

&lt;p&gt;Workshop mode retains the Specrew workshop approach; Parallel scan mode is an addition made for this standalone adaptation.&lt;/p&gt;

&lt;p&gt;One rule I especially like is &lt;strong&gt;render before you ask&lt;/strong&gt;. The agent should not ask you to approve an architecture, component map, set of options, or design verdict that it has not actually shown and explained. When a diagram or shared view helps, it must be visible in the conversation before the approval question.&lt;/p&gt;

&lt;p&gt;The workshop is also collaborative. For architecture and component work, the agent should not immediately hand down a finished solution. It builds the component map with you, walks an important flow through it, and then discusses the remaining trade-offs.&lt;/p&gt;

&lt;p&gt;Once the selected lenses — or the load-bearing concerns surfaced by a parallel scan — have been discussed, the skill summarizes the design context, decisions, components, important flows, alternatives, risks, open questions, and validation signals. Then it stops. Planning and implementation remain separate steps that I can start when I am ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use it
&lt;/h2&gt;

&lt;p&gt;The skill activates only when you explicitly ask for a design workshop. It is useful when you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deepen a technical design before planning;&lt;/li&gt;
&lt;li&gt;challenge an existing direction;&lt;/li&gt;
&lt;li&gt;compare two possible designs;&lt;/li&gt;
&lt;li&gt;uncover cross-cutting concerns or missing decisions;&lt;/li&gt;
&lt;li&gt;prepare a design for review with another engineer or architect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not intended for every coding request. Asking an agent to implement a small change or write an implementation plan should not silently start a workshop.&lt;/p&gt;

&lt;p&gt;After dogfooding the skill, I do not expect to run the full workshop for every feature. Its depth is the point, but that depth also takes time.&lt;/p&gt;

&lt;p&gt;I would reach for Workshop mode when starting a new product or subsystem, entering an unfamiliar part of a system, or designing a genuinely large feature. Knowing how a feature should behave does not necessarily mean that its implementation model is clear: gaps may remain in the data model, ownership, usability, security, or operations.&lt;/p&gt;

&lt;p&gt;If I want a faster broad review from several perspectives and do not mind spending more model tokens, I can start with Parallel scan mode. On hosts with subagent support, it front-loads the research in parallel; afterward, I can take the important points into Workshop mode one by one.&lt;/p&gt;

&lt;p&gt;After installation, I can invoke the skill explicitly with &lt;code&gt;/design-lens-workshop&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/design-lens-workshop Workshop the technical design of a new caching subsystem
for product catalog reads before we plan implementation.

Product updates can arrive at any time, stale prices are unacceptable, and
the API must remain available when the catalog provider is slow. Inspect the
current repository first, then start with a design brief and workshop agenda.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or I can start with Parallel scan mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/design-lens-workshop Run a parallel scan for this caching subsystem first.
Inspect the current repository and surface the main concerns and questions
across the relevant lenses.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install and try it
&lt;/h2&gt;

&lt;p&gt;The skill is available in the public &lt;a href="https://github.com/Zio-Net/skills" rel="noopener noreferrer"&gt;ZioNet skills repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Preview it with GitHub CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh skill preview Zio-Net/skills design-lens-workshop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install it for Codex at project scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh skill &lt;span class="nb"&gt;install &lt;/span&gt;Zio-Net/skills design-lens-workshop &lt;span class="nt"&gt;--agent&lt;/span&gt; codex &lt;span class="nt"&gt;--scope&lt;/span&gt; project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude Code, replace &lt;code&gt;codex&lt;/code&gt; with &lt;code&gt;claude-code&lt;/code&gt;. The repository follows the open Agent Skills structure, so the skill can also be copied manually into the appropriate skills directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  In conclusion
&lt;/h2&gt;

&lt;p&gt;Specrew remains a complete approach to governing an AI-assisted software development lifecycle. The standalone skill does not try to replace that.&lt;/p&gt;

&lt;p&gt;It offers another way to use one particularly useful idea from Specrew: a structured, collaborative technical design workshop that can be invoked at the point where it is useful.&lt;/p&gt;

&lt;p&gt;For me, that means having an AI agent help with a familiar tech-lead responsibility — looking at a design from the relevant angles when the work deserves that depth, making trade-offs explicit, and reaching the implementation plan with fewer hidden decisions.&lt;/p&gt;

&lt;p&gt;If you try it, I would be glad to hear your feedback and ideas for improving it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The skill is a standalone adaptation of the Specrew Design Workshop under the MIT License, with attribution to Alon Fliess, Specrew, and its contributors. It is maintained in &lt;a href="https://github.com/Zio-Net/skills" rel="noopener noreferrer"&gt;&lt;code&gt;Zio-Net/skills&lt;/code&gt;&lt;/a&gt; and is not an official Specrew distribution.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>agents</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>Where Should Your Agent Run? Three Patterns with Microsoft Foundry</title>
      <dc:creator>Venya Brodetskiy</dc:creator>
      <pubDate>Sat, 01 Aug 2026 16:41:58 +0000</pubDate>
      <link>https://dev.to/venyabrodetskiy/where-should-your-agent-run-three-patterns-with-microsoft-foundry-28b8</link>
      <guid>https://dev.to/venyabrodetskiy/where-should-your-agent-run-three-patterns-with-microsoft-foundry-28b8</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical comparison of who owns the code, state, deployment, and telemetry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my previous articles, I covered &lt;a href="https://dev.to/venyabrodetskiy/getting-started-with-microsoft-agent-framework-25l2"&gt;individual agents&lt;/a&gt; and &lt;a href="https://dev.to/venyabrodetskiy/building-multi-step-workflows-with-agent-framework-4lk8"&gt;multi-step workflows&lt;/a&gt; with Microsoft Agent Framework. Once the prompt works and the tools return the right data, the next question sounds simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where should this agent actually run?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That decision changes more than deployment. It determines who owns the process, conversation state, scaling, identity, and telemetry. It also determines which problems your team will debug when the agent fails at 2 a.m.&lt;/p&gt;

&lt;p&gt;The accompanying &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo" rel="noopener noreferrer"&gt;demo repository&lt;/a&gt; contains three deliberately different patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A declarative prompt agent managed by Foundry.&lt;/li&gt;
&lt;li&gt;Custom Agent Framework code deployed to a Foundry-hosted runtime.&lt;/li&gt;
&lt;li&gt;The same kind of custom code running on your own infrastructure and exporting telemetry to Foundry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are not three copies of the same agent. They are three different ownership choices.&lt;/p&gt;

&lt;p&gt;To keep the comparison practical, demos 2 and 3 use the same two C# tools: a weather function and an office-climate function. The user can ask the same question and receive the same deterministic result. What changes is everything around the tool call—how the process starts, where state lives, how it is deployed, and who must make its trace visible. Demo 1 intentionally stays simpler: it has instructions and managed conversation state, but no custom tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The runtime spectrum
&lt;/h2&gt;

&lt;p&gt;Think of the options as a spectrum. Moving from left to right gives you more control, but also more operational responsibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqopsqshqrp39dceo7nm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqopsqshqrp39dceo7nm.png" alt="Three agent runtime patterns comparing ownership of the definition, runtime, conversation state, deployment, and telemetry" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From a prompt agent to self-hosted code: more infrastructure control also means more to operate. &lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0yrvw2pivecoxebzi9xz.png" rel="noopener noreferrer"&gt;Open in full size&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The useful question is not “Which option is best?” It is “Which responsibilities do I actually need to own?”&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: let Foundry run a prompt agent
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/prompt-agent" rel="noopener noreferrer"&gt;Foundry prompt agent&lt;/a&gt; is a declarative, configuration-driven agent—the closest of these three patterns to a no-code option. In the Foundry portal, you can select a model, write instructions, attach supported tools, and test the agent in the Playground without writing application code or using Microsoft Agent Framework.&lt;/p&gt;

&lt;p&gt;That does not make it a toy. You can improve its instructions with the &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/prompt-optimizer" rel="noopener noreferrer"&gt;Prompt Optimizer&lt;/a&gt; (currently in preview), ground responses in your own documents with &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/file-search" rel="noopener noreferrer"&gt;file search&lt;/a&gt;, and attach tools for search, code execution, APIs, MCP servers, and other actions. Custom function tools are supported too, but they are not fully no-code: your application or another service must execute the function code.&lt;/p&gt;

&lt;p&gt;The trade-off is control. You can configure the model, instructions, knowledge, and supported tools, but Foundry owns the orchestration loop and runtime. If you need arbitrary application logic or custom orchestration, a code-based agent is the better fit.&lt;/p&gt;

&lt;p&gt;The portal is only one authoring path. The same agent can also be created and versioned through the Foundry SDK or REST API. This demo uses C# so the definition is reproducible in source control, but that code creates a managed Foundry resource—it is not a runtime you host yourself.&lt;/p&gt;

&lt;p&gt;The demo creates a small pirate assistant. The persona is not important; the definition is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ProjectsAgentDefinition&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DeclarativeAgentDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentInstructions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;ProjectsAgentVersion&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentAdministrationClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAgentVersionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every update creates a new version under the same agent name. Whether you author the definition in the portal or in code, Foundry runs the agent; your application only connects to it.&lt;/p&gt;

&lt;p&gt;The repository does use Microsoft Agent Framework on the client side. &lt;code&gt;AsAIAgent(agentRecord)&lt;/code&gt; adapts the managed prompt agent to the common &lt;code&gt;AIAgent&lt;/code&gt; API, which the console uses to invoke the agent and resume an &lt;code&gt;AgentSession&lt;/code&gt;. It does not turn Demo 1 into a code-hosted agent: &lt;code&gt;Azure.AI.Projects&lt;/code&gt; still creates the Foundry resource, and Foundry still owns the runtime.&lt;/p&gt;

&lt;p&gt;There is a small detail in this demo that explains the ownership model well. The local file stores only a Foundry conversation ID. The message history remains in the cloud. On restart, the console app reads that ID and reconnects to the same conversation.&lt;/p&gt;

&lt;p&gt;You can see both pieces in the repository: the &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo/blob/main/1-FoundryPromptAgent/Program.cs" rel="noopener noreferrer"&gt;prompt-agent setup&lt;/a&gt; and the small &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo/blob/main/1-FoundryPromptAgent/FileConversationStore.cs" rel="noopener noreferrer"&gt;conversation ID store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzjqia11b02wteg5upjp6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzjqia11b02wteg5upjp6.png" alt="Microsoft Foundry Playground showing a prompt-only pirate agent explaining its managed runtime, lack of custom tools, and model-only limitation" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The prompt agent stays intentionally simple: Foundry runs it, keeps the conversation state, and the agent answers without custom code tools. &lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/9xclhrn3673y2ioyi7xt.png" rel="noopener noreferrer"&gt;Open in full size&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With project monitoring enabled, Foundry records the prompt agent's server-side trace automatically—no instrumentation code required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3k88wcp1ogivcm973pnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3k88wcp1ogivcm973pnp.png" alt="Microsoft Foundry trace view for pirate-prompt-agent, showing the agent invocation and underlying model call" width="799" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The trace shows the agent invocation, model call, duration, and token usage. &lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/7k9w5pcvtzuvli2q2xkz.png" rel="noopener noreferrer"&gt;Open in full size&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose this pattern when&lt;/strong&gt; instructions and service-supported tools describe most of the agent, and you want the smallest runtime surface to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: bring your code, let Foundry host it
&lt;/h2&gt;

&lt;p&gt;The second demo needs custom C# functions. It exposes deterministic weather data and mock office-climate data, then packages that logic as a Foundry hosted agent.&lt;/p&gt;

&lt;p&gt;Here, you own the agent code and tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AIProjectClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Use the appropriate demo tool and keep answers concise."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;AIFunctionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WeatherTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetWeather&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;AIFunctionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WeatherTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetOfficeClimateStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AgentHost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFoundryResponses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterProtocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"responses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapFoundryResponses&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AddFoundryResponses&lt;/code&gt; connects the agent to the Responses protocol handler, while &lt;code&gt;MapFoundryResponses&lt;/code&gt; exposes the &lt;code&gt;/responses&lt;/code&gt; endpoint. According to the &lt;a href="https://learn.microsoft.com/en-us/agent-framework/hosting/foundry-hosted-agent" rel="noopener noreferrer"&gt;Agent Framework hosting documentation&lt;/a&gt;, Responses is the recommended starting protocol for most conversational agents. Foundry manages conversation history, streaming, and session lifecycle around that endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is happening here?&lt;/strong&gt; The &lt;code&gt;AIAgent&lt;/code&gt; still contains normal application logic. &lt;code&gt;AgentHost&lt;/code&gt; supplies the hosting boundary, and the Responses adapter supplies the HTTP contract. This separation is useful: the tool methods do not need to know whether the process is running locally or inside Foundry.&lt;/p&gt;

&lt;p&gt;Local execution has one extra concern. A request sent directly to &lt;code&gt;localhost&lt;/code&gt; does not contain the user and chat isolation context injected by Foundry, so the demo registers a local fallback session-isolation provider. That fallback exists to make direct development calls work; the deployed platform provides the real request context.&lt;/p&gt;

&lt;p&gt;The source manifest declares the hosted-agent kind, Responses protocol, CPU and memory, and environment variables. During initialization, &lt;code&gt;azd&lt;/code&gt; turns it into deployment configuration that also specifies the .NET runtime, entry point, and remote build. From the demo directory, the intended deployment flow is short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;azd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\agent.manifest.yaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--deploy-mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;azd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;azd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is the AC working in Venya's home office?"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer a UI, the &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/vs-code-agents-workflow-pro-code" rel="noopener noreferrer"&gt;Microsoft Foundry Toolkit for Visual Studio Code&lt;/a&gt; provides a &lt;code&gt;Foundry Toolkit: Deploy Hosted Agent&lt;/code&gt; workflow. It supports both code and container deployment and lets you review the settings before deploying, so the &lt;code&gt;azd&lt;/code&gt; commands are not the only path.&lt;/p&gt;

&lt;p&gt;A useful mental model is &lt;strong&gt;serverless containers&lt;/strong&gt;: you provide code or an image, while Foundry manages the infrastructure and scaling. This demo uses the code path, so &lt;code&gt;azd&lt;/code&gt; or the Toolkit uploads the project as a ZIP and Foundry performs the .NET build remotely.&lt;/p&gt;

&lt;p&gt;Under the hood, the result still runs in a platform-provided container on Azure Container Apps, inside a per-session VM-isolated sandbox. Idle compute can scale to zero, while session files are restored when the session resumes. The &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents" rel="noopener noreferrer"&gt;Hosted agents documentation&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/agents-networking-deep-dive" rel="noopener noreferrer"&gt;networking deep dive&lt;/a&gt; cover the Container Apps and Micro VM layers in more detail.&lt;/p&gt;

&lt;p&gt;The agent code is still yours. The running environment is not. Foundry gives the deployed agent an identity and endpoint and supplies platform configuration such as the project endpoint and Application Insights connection string. Microsoft describes the managed Hosted Agents service as generally available, while this sample still pins preview versions of some .NET integration packages.&lt;/p&gt;

&lt;p&gt;The complete &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo/blob/main/2-FoundryHostedMafAgent/Program.cs" rel="noopener noreferrer"&gt;hosted-agent program&lt;/a&gt;, &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo/blob/main/2-FoundryHostedMafAgent/agent.manifest.yaml" rel="noopener noreferrer"&gt;deployment manifest&lt;/a&gt;, and &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo/blob/main/2-FoundryHostedMafAgent/README.md" rel="noopener noreferrer"&gt;demo commands&lt;/a&gt; are kept together in the repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fij35c03m1lt7g5hu41sv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fij35c03m1lt7g5hu41sv.png" alt="Microsoft Foundry Playground showing foundry-hosted-maf-agent answering an office-climate question with deterministic FAC-1042 data" width="799" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Foundry-hosted Agent Framework agent executes the custom &lt;code&gt;get_office_climate_status&lt;/code&gt; C# function and returns the deterministic &lt;code&gt;FAC-1042&lt;/code&gt; result. &lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xtg65inlodui2gzogs9v.png" rel="noopener noreferrer"&gt;Open in full size&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With project monitoring enabled, the Foundry hosting integration exports OpenTelemetry traces automatically, so this demo needs no separate tracing setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxg54cx8trol0rf2l39j1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxg54cx8trol0rf2l39j1.png" alt="Microsoft Foundry trace view for foundry-hosted-maf-agent, showing automatically collected protocol, identity, model, and storage spans" width="799" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The hosted trace also reveals protocol, identity, model, and platform storage spans. &lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rohgetmdgixill9oinv8.png" rel="noopener noreferrer"&gt;Open in full size&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose this pattern when&lt;/strong&gt; you need custom code and tools but do not want to operate the agent runtime yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: keep the runtime, export the telemetry
&lt;/h2&gt;

&lt;p&gt;The third demo uses the same weather and office-climate scenario, but the process runs locally—or anywhere you choose to deploy it. That means your team owns process lifetime, scaling, secrets, networking, and state.&lt;/p&gt;

&lt;p&gt;It also means telemetry is your responsibility. The demo creates an OpenTelemetry resource with a stable agent identity and exports traces to Azure Monitor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;agentId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"local-weather-agent"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ResourceBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAttributes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gen_ai.agent.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gen_ai.agent.name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tracerProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTracerProviderBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetResourceBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Experimental.Microsoft.Agents.AI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Experimental.Microsoft.Extensions.AI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureMonitorTraceExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;applicationInsightsConnectionString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your process sends OpenTelemetry traces directly to Application Insights. Registering it as an &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/register-external-agent" rel="noopener noreferrer"&gt;external agent&lt;/a&gt; does not deploy or host the code; it simply tells Foundry which traces belong to that agent. The key link is a stable ID: the &lt;code&gt;gen_ai.agent.id&lt;/code&gt; emitted by the runtime must match the ID used during registration.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/VenyaBrodetskiy/AfterBuildDemo/blob/main/3-LocalMafAgent/Program.cs" rel="noopener noreferrer"&gt;self-hosted demo&lt;/a&gt; shows the exporter, stable agent ID, two tools, and local conversation loop in one file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx99s6x0sxb0pr60qkmcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx99s6x0sxb0pr60qkmcb.png" alt="Microsoft Foundry trace view for local-weather-agent showing the agent run, model calls, and a separate execute_tool span" width="799" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The self-hosted trace separates the agent run, two model calls, and the office-climate tool execution. &lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/f351033wzfsnimtkq6na.png" rel="noopener noreferrer"&gt;Open in full size&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;execute_tool&lt;/code&gt; is a separate span. Foundry shows which function ran, how long it took, and—when sensitive-data capture is enabled—its input and output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose this pattern when&lt;/strong&gt; infrastructure control, private networking, portability, or a custom hosting platform matters more than managed operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  State is not one thing
&lt;/h2&gt;

&lt;p&gt;“Foundry manages state” is an easy phrase to overread. In practice, there are at least three different concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conversation history:&lt;/strong&gt; the messages exchanged with the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent session:&lt;/strong&gt; the technical context used to continue that conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business state:&lt;/strong&gt; orders, approvals, user preferences, workflow checkpoints, and anything else your application must preserve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the prompt-agent demo, Foundry stores the conversation while the console application saves only its ID. With Foundry-hosted code, the Responses protocol manages conversational history and session lifecycle around the endpoint. Neither pattern turns Foundry into a database for your domain model: durable business state still belongs in your application and data store.&lt;/p&gt;

&lt;p&gt;In the self-hosted demo, the current &lt;code&gt;AgentSession&lt;/code&gt; exists inside the running process. If that process restarts, persistence and recovery are your responsibility. This leads to a more useful design question than simply asking whether a platform “has state”: &lt;strong&gt;what must survive a process restart, deployment, or regional failure?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Prompt agent&lt;/th&gt;
&lt;th&gt;Foundry-hosted code&lt;/th&gt;
&lt;th&gt;Self-hosted code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Mostly declarative behavior&lt;/td&gt;
&lt;td&gt;Custom code, managed runtime&lt;/td&gt;
&lt;td&gt;Maximum infrastructure control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime owner&lt;/td&gt;
&lt;td&gt;Foundry&lt;/td&gt;
&lt;td&gt;Foundry&lt;/td&gt;
&lt;td&gt;Your team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment unit&lt;/td&gt;
&lt;td&gt;Agent definition&lt;/td&gt;
&lt;td&gt;Code package/container&lt;/td&gt;
&lt;td&gt;Your application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversation state&lt;/td&gt;
&lt;td&gt;Foundry-managed&lt;/td&gt;
&lt;td&gt;Protocol/platform-managed&lt;/td&gt;
&lt;td&gt;Your implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telemetry&lt;/td&gt;
&lt;td&gt;Platform-managed&lt;/td&gt;
&lt;td&gt;Hosting integration&lt;/td&gt;
&lt;td&gt;You configure export and identity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The middle option is often the practical default for a code-based agent: you keep normal application code without immediately taking ownership of another production service. But the table is not a maturity ladder. Self-hosting is not “more advanced,” and a prompt agent is not “just a demo.” Each option optimizes for a different ownership boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo notes
&lt;/h2&gt;

&lt;p&gt;The weather and office-climate tools return deterministic fake data. The self-hosted demo also enables sensitive telemetry content so prompts and tool results are easier to inspect. Keep that setting disabled by default unless your privacy and retention requirements allow it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Choosing an agent runtime is really choosing an ownership boundary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;prompt agent&lt;/strong&gt; when the definition is the product.&lt;/li&gt;
&lt;li&gt;Use a &lt;strong&gt;Foundry-hosted agent&lt;/strong&gt; when custom code is the product but operating the runtime is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-host&lt;/strong&gt; when you need to own the environment—and accept responsibility for state, deployment, and telemetry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The durable idea is simple: &lt;strong&gt;choose who owns the runtime, then keep a consistent telemetry contract.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>foundry</category>
      <category>microsoftagentframework</category>
      <category>observability</category>
      <category>agents</category>
    </item>
    <item>
      <title>Building Multi-Step Workflows with Agent Framework</title>
      <dc:creator>Venya Brodetskiy</dc:creator>
      <pubDate>Sat, 01 Aug 2026 12:27:34 +0000</pubDate>
      <link>https://dev.to/venyabrodetskiy/building-multi-step-workflows-with-agent-framework-4lk8</link>
      <guid>https://dev.to/venyabrodetskiy/building-multi-step-workflows-with-agent-framework-4lk8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://medium.com/@venya-brodetskiy/building-multi-step-workflows-with-agent-framework-d22967d0503f" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on December 28, 2025.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/venyabrodetskiy/getting-started-with-microsoft-agent-framework-25l2"&gt;previous article&lt;/a&gt;, we explored how to build individual AI agents with Microsoft Agent Framework — agents that can call functions, extract structured data, and leverage RAG. These are powerful building blocks, but real-world scenarios often require something more: orchestrating multiple agents and deterministic logic into complex, multi-step processes.&lt;/p&gt;

&lt;p&gt;That's where Agent Framework's workflow system comes&amp;nbsp;in.&lt;/p&gt;

&lt;p&gt;Think about a customer support email system. A single agent can't handle everything. You need to preprocess the email, classify it, apply business rules, route it appropriately, draft responses, and sometimes escalate to humans. Each step has different requirements — some need AI reasoning, others need deterministic logic, and all need to work together seamlessly.&lt;/p&gt;

&lt;p&gt;In this guide, we'll build exactly that: a customer support email triage workflow that combines LLM agents with business logic to automatically process, classify, and respond to customer&amp;nbsp;emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Workflows?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp9y1oq44x98aa9bab2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp9y1oq44x98aa9bab2n.png" alt="Hand-drawn workflow graph with branching and merging paths between rectangular nodes." width="641" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Workflows in Agent Framework are graph-based orchestration systems. Instead of writing monolithic code that tries to handle every scenario, you build a directed graph&amp;nbsp;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Executors&lt;/strong&gt; are individual processing units (agents or custom&amp;nbsp;logic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges&lt;/strong&gt; connect executors and define the flow of&amp;nbsp;data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditions&lt;/strong&gt; on edges enable dynamic routing based on&amp;nbsp;context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture gives&amp;nbsp;you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modularity&lt;/strong&gt;: Each executor focuses on one&amp;nbsp;task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarity&lt;/strong&gt;: The graph structure makes the process flow&amp;nbsp;explicit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Conditional edges adapt to different scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt;: Changes to one step don’t cascade through the entire&amp;nbsp;system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more on workflows, see the &lt;a href="https://learn.microsoft.com/en-us/agent-framework/user-guide/workflows/overview" rel="noopener noreferrer"&gt;Agent Framework Workflows Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Use Case: Customer Support Email&amp;nbsp;Triage
&lt;/h2&gt;

&lt;p&gt;Let’s define our business problem. We receive hundreds of customer support emails daily. We want&amp;nbsp;to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Automatically process routine&amp;nbsp;requests&lt;/li&gt;
&lt;li&gt; Apply business rules consistently&lt;/li&gt;
&lt;li&gt; Escalate appropriately when human judgment is&amp;nbsp;needed&lt;/li&gt;
&lt;li&gt; Maintain compliance with data protection and&amp;nbsp;policies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the workflow we’ll&amp;nbsp;build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3nnfq23mg1xo3rovupps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3nnfq23mg1xo3rovupps.png" alt="Customer support workflow diagram routing requests through preprocessing, LLM classification, policy rules, automated response, human escalation, or refund handling to a final summary." width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workflow handles four routing scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-priority escalations&lt;/strong&gt;: Negative sentiment + high urgency → human&amp;nbsp;handoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarification needed&lt;/strong&gt;: Missing information → agent drafts questions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refund requests:&lt;/strong&gt; Automated refund creation → human&amp;nbsp;review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normal replies:&lt;/strong&gt; Standard responses → agent drafts&amp;nbsp;reply&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workflow Architecture: The Building&amp;nbsp;Blocks
&lt;/h2&gt;

&lt;p&gt;Before diving into code, let’s understand the three core concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executors
&lt;/h2&gt;

&lt;p&gt;An executor is a processing unit that takes an input, performs some operation, and returns an output. Every executor inherits from Executor&amp;lt;TInput, TOutput&amp;gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal sealed class PreprocessEmailExecutor : Executor&amp;lt;string, EmailDocument&amp;gt;
{
    public override async ValueTask&amp;lt;EmailDocument&amp;gt; HandleAsync(
        string message,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        // Process the raw email...
        return processedEmail;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Executors can&amp;nbsp;be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic&lt;/strong&gt;: Pure logic, no AI (preprocessing, routing, validation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic&lt;/strong&gt;: Uses LLM for reasoning (classification, response drafting)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Edges
&lt;/h2&gt;

&lt;p&gt;Edges connect executors and can include conditions for dynamic&amp;nbsp;routing:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var workflow = new WorkflowBuilder(startExecutor)
    .AddEdge(preprocess, intake)  // Simple edge
    .AddEdge&amp;lt;PolicyContext&amp;gt;(      // Conditional edge
        source: policyGate,
        target: responder,
        condition: ctx =&amp;gt; ctx.Policy.Mode == ResponseMode.DraftReply)
    .Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Conditions let you build branching logic: “If sentiment is negative AND urgency is high, escalate to&amp;nbsp;human.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared State
&lt;/h2&gt;

&lt;p&gt;Executors can communicate through shared state. This is crucial when multiple executors need access to the same&amp;nbsp;data:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Write to shared state
await context.QueueStateUpdateAsync(
    SupportRunState.KeyEmail,
    email,
    scopeName: SupportRunState.ScopeName
);

// Read from shared state
var email = await context.ReadStateAsync&amp;lt;EmailDocument&amp;gt;(
    SupportRunState.KeyEmail,
    scopeName: SupportRunState.ScopeName
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Deterministic vs. Agentic Executors
&lt;/h2&gt;

&lt;p&gt;Let’s compare two executors to see the difference in approach.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deterministic Executor: PreprocessEmailExecutor
&lt;/h3&gt;

&lt;p&gt;This executor uses pure C# logic to clean emails and detect&amp;nbsp;PII:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal sealed partial class PreprocessEmailExecutor : Executor&amp;lt;string, EmailDocument&amp;gt;
{
    public override async ValueTask&amp;lt;EmailDocument&amp;gt; HandleAsync(
        string message,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        var lines = SplitLines(message);
        // Extract headers
        string? from = TryExtractHeaderValue(lines, "From:");
        string? subject = TryExtractHeaderValue(lines, "Subject:");
        // Clean the body
        var body = RemoveHeaders(lines);
        body = StripQuotedReplies(body);
        body = NormalizeWhitespace(body);
        // Detect PII using regex
        var detectedEmails = EmailRegex().Matches(body)...;
        var detectedPhones = PhoneRegex().Matches(body)...;
        var detectedOrderIds = OrderIdRegex().Matches(body)...;
        // Mask PII for model safety
        var modelSafe = MaskPii(body);
        var email = new EmailDocument
        {
            OriginalText = message,
            CleanText = body,
            ModelSafeText = modelSafe,
            ContainsPii = detectedEmails.Count &amp;gt; 0 || detectedPhones.Count &amp;gt; 0,
            DetectedEmails = detectedEmails,
            DetectedPhones = detectedPhones,
            DetectedOrderIds = detectedOrderIds
        };
        // Store in shared state and emit event
        await context.QueueStateUpdateAsync(SupportRunState.KeyEmail, email, ...);
        await context.AddEventAsync(new EmailPreprocessedEvent(email), ...);
        return email;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What makes it deterministic?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses regex patterns to detect emails, phones, and order&amp;nbsp;IDs&lt;/li&gt;
&lt;li&gt;Applies consistent text cleaning&amp;nbsp;rules&lt;/li&gt;
&lt;li&gt;No LLM calls — predictable, fast, and cost-free&lt;/li&gt;
&lt;li&gt;Perfect for operations where you need guaranteed behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Agentic Executor: EmailIntakeExecutor
&lt;/h3&gt;

&lt;p&gt;This executor uses an LLM to classify the&amp;nbsp;email:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal sealed class EmailIntakeExecutor : Executor&amp;lt;EmailDocument, IntakeContext&amp;gt;
{
    private readonly AIAgent _agent;
    private readonly AgentThread _thread;

    public EmailIntakeExecutor(string id, IChatClient chatClient) : base(id)
    {
        ChatClientAgentOptions agentOptions = new()
        {
            ChatOptions = new()
            {
                Instructions = """
                    You are a customer support intake assistant.
                    Return JSON that matches the schema exactly.
                    Be concise and do not invent missing facts.
                    """,
                ResponseFormat = ChatResponseFormat.ForJsonSchema&amp;lt;IntakeResult&amp;gt;()
            }
        };

        _agent = new ChatClientAgent(chatClient, agentOptions);
        _thread = _agent.GetNewThread();
    }

    public override async ValueTask&amp;lt;IntakeContext&amp;gt; HandleAsync(
        EmailDocument message,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        var prompt = $"""
            Classify this inbound customer support email.

            Subject: {message.Subject ?? "(none)"}
            From: {message.From ?? "(unknown)"}

            Email:
            {message.ModelSafeText}
            """;

        var result = await _agent.RunAsync(prompt, _thread, cancellationToken: cancellationToken);
        var intake = JsonSerializer.Deserialize&amp;lt;IntakeResult&amp;gt;(result.Text);

        var intakeContext = new IntakeContext { Email = message, Intake = intake };
        await context.QueueStateUpdateAsync(SupportRunState.KeyIntake, intakeContext, ...);
        await context.AddEventAsync(new IntakeCompletedEvent(intakeContext), ...);

        return intakeContext;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What makes it&amp;nbsp;agentic?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses an LLM to understand the email’s intent, urgency, and sentiment&lt;/li&gt;
&lt;li&gt;Extracts structured data through ForJsonSchema&amp;lt;IntakeResult&amp;gt;()&lt;/li&gt;
&lt;li&gt;Handles nuance and context that regex can’t&amp;nbsp;capture&lt;/li&gt;
&lt;li&gt;Perfect for classification, reasoning, and natural language understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Hybrid&amp;nbsp;Approach&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By combining both types of executors, you get the best of both&amp;nbsp;worlds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic steps&lt;/strong&gt; provide speed, consistency, and cost&amp;nbsp;control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic steps&lt;/strong&gt; handle complexity, nuance, and reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Together&lt;/strong&gt;, they create a system that’s both intelligent and&amp;nbsp;reliable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conditional Routing: PolicyGateExecutor
&lt;/h2&gt;

&lt;p&gt;The PolicyGateExecutor demonstrates how to implement business logic&amp;nbsp;routing:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal sealed class PolicyGateExecutor : Executor&amp;lt;IntakeContext, PolicyContext&amp;gt;
{
    public override async ValueTask&amp;lt;PolicyContext&amp;gt; HandleAsync(
        IntakeContext message,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        // Apply business rules
        var missingInfo = message.Intake.MissingInformation ?? [];
        var mode = missingInfo.Count &amp;gt; 0
            ? ResponseMode.AskClarifyingQuestions
            : ResponseMode.DraftReply;

        var sla = message.Intake.Urgency switch
        {
            UrgencyLevel.High =&amp;gt; "4h",
            UrgencyLevel.Normal =&amp;gt; "24h",
            _ =&amp;gt; "72h"
        };

        var complianceNotes = new List&amp;lt;string&amp;gt;();

        if (message.Email.ContainsPii)
        {
            complianceNotes.Add("PII detected. Use only redacted content in replies.");
        }

        if (message.Intake.Intent is UserIntent.Refund or UserIntent.CancelOrder)
        {
            complianceNotes.Add("Do not promise refund. Confirm policy first.");
        }

        // Build policy decision
        var policy = new PolicyDecision
        {
            Mode = mode,
            RedactedEmailText = message.Email.ModelSafeText,
            Sla = sla,
            ComplianceNotes = complianceNotes
        };

        var policyContext = new PolicyContext
        {
            Email = message.Email,
            Intake = message.Intake,
            Policy = policy
        };

        // Store routing decision in shared state
        var isEscalation = policyContext.Intake.Sentiment == Sentiment.Negative
                           &amp;amp;&amp;amp; policyContext.Intake.Urgency == UrgencyLevel.High;
        var isRefund = policyContext.Policy.Mode == ResponseMode.DraftReply
                       &amp;amp;&amp;amp; policyContext.Intake.Intent == UserIntent.Refund
                       &amp;amp;&amp;amp; !isEscalation;

        var route = isEscalation ? "Human escalation"
                  : isRefund ? "Refund request (human review)"
                  : "Normal reply";

        await context.QueueStateUpdateAsync(SupportRunState.KeySelectedRoute, route, ...);

        return policyContext;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This executor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Evaluates intake results&lt;/strong&gt; to determine response&amp;nbsp;mode&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Applies SLA&lt;/strong&gt; rules based on&amp;nbsp;urgency&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enforces compliance&lt;/strong&gt; by flagging PII and sensitive requests&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Determines routing&lt;/strong&gt; through conditional logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The workflow builder then uses these decisions to route appropriately:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return new WorkflowBuilder(preprocess)
    .AddEdge(preprocess, intake)
    .AddEdge(intake, policyGate)

    // Escalate: negative + high urgency
    .AddEdge&amp;lt;PolicyContext&amp;gt;(
        source: policyGate,
        target: humanPrep,
        condition: ctx =&amp;gt; ctx.Intake.Sentiment == Sentiment.Negative
                          &amp;amp;&amp;amp; ctx.Intake.Urgency == UrgencyLevel.High)

    // Refund: no missing info + refund intent
    .AddEdge&amp;lt;PolicyContext&amp;gt;(
        source: policyGate,
        target: refundRequest,
        condition: ctx =&amp;gt; ctx.Policy.Mode == ResponseMode.DraftReply
                          &amp;amp;&amp;amp; ctx.Intake.Intent == UserIntent.Refund
                          &amp;amp;&amp;amp; !(ctx.Intake.Sentiment == Sentiment.Negative
                               &amp;amp;&amp;amp; ctx.Intake.Urgency == UrgencyLevel.High))

    // Default: normal reply
    .AddEdge&amp;lt;PolicyContext&amp;gt;(
        source: policyGate,
        target: responder,
        condition: ctx =&amp;gt; ctx.Policy.Mode == ResponseMode.DraftReply
                          &amp;amp;&amp;amp; ctx.Intake.Intent != UserIntent.Refund
                          &amp;amp;&amp;amp; !(ctx.Intake.Sentiment == Sentiment.Negative
                               &amp;amp;&amp;amp; ctx.Intake.Urgency == UrgencyLevel.High))
    .Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Custom Events: Building Your Observability Story
&lt;/h2&gt;

&lt;p&gt;One of the most powerful features of Agent Framework workflows is the custom events system. Events provide real-time insight into what’s happening inside your workflow, making debugging and monitoring dramatically easier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating Custom&amp;nbsp;Events
&lt;/h2&gt;

&lt;p&gt;Custom events inherit from WorkflowEvent and can carry any data you&amp;nbsp;need:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;internal sealed class EmailPreprocessedEvent(EmailDocument email) : WorkflowEvent(email)
{
    public EmailDocument Email { get; } = email;
}

internal sealed class IntakeCompletedEvent(IntakeContext context) : WorkflowEvent(context)
{
    public IntakeContext Context { get; } = context;
}

internal sealed class PolicyAppliedEvent(PolicyContext context) : WorkflowEvent(context)
{
    public PolicyContext Context { get; } = context;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These events are simple data carriers, but they transform how you observe workflow execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emitting Events from Executors
&lt;/h2&gt;

&lt;p&gt;Inside any executor, you emit events through the workflow&amp;nbsp;context:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public override async ValueTask&amp;lt;EmailDocument&amp;gt; HandleAsync(
    string message,
    IWorkflowContext context,
    CancellationToken cancellationToken = default)
{
    // ... process the email ...

    var email = new EmailDocument
    {
        OriginalText = message,
        CleanText = body,
        ModelSafeText = modelSafe,
        ContainsPii = containsPii,
        // ... other properties
    };

    // Emit the event
    await context.AddEventAsync(new EmailPreprocessedEvent(email), cancellationToken);

    return email;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Events are emitted in real-time as the workflow executes, allowing you to observe progress as it&amp;nbsp;happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming Events: Real-Time Workflow Monitoring
&lt;/h2&gt;

&lt;p&gt;Here’s where it gets interesting. When you run a workflow, you can watch the event&amp;nbsp;stream:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: email);

await foreach (var evt in run.WatchStreamAsync())
{
    switch (evt)
    {
        case EmailPreprocessedEvent e:
            Console.WriteLine(
                $"[Preprocess|Deterministic] Subject='{e.Email.Subject}' | " +
                $"PII={e.Email.ContainsPii} | OrderIds={e.Email.DetectedOrderIds.Count}");
            break;

        case IntakeCompletedEvent e:
            Console.WriteLine(
                $"[Intake|Agent] {e.Context.Intake.Category} | " +
                $"{e.Context.Intake.Urgency} | {e.Context.Intake.Sentiment}");
            Console.WriteLine($"  Summary: {e.Context.Intake.Summary}");
            break;

        case PolicyAppliedEvent e:
            Console.WriteLine(
                $"[Policy|Deterministic] Mode={e.Context.Policy.Mode} | " +
                $"SLA={e.Context.Policy.Sla}");
            break;

        case ResponseDraftedEvent e:
            Console.WriteLine(
                $"[Responder|Agent] Generated {e.Info.Mode} response");
            break;

        case HumanHandoffPreparedEvent e:
            Console.WriteLine(
                $"[Human Prep|Hybrid] Queue='{e.Package.Queue}' | " +
                $"SLA={e.Package.Sla}");
            Console.WriteLine($"  Summary: {e.Package.Summary}");
            break;

        case RefundRequestCreatedEvent e:
            Console.WriteLine(
                $"[Refund|Deterministic] Id={e.Request.RefundRequestId}");
            break;

        case WorkflowOutputEvent outputEvent:
            Console.WriteLine("=== Workflow Output ===");
            Console.WriteLine(outputEvent.Data);
            break;

        case WorkflowErrorEvent errorEvent:
            Console.WriteLine($"ERROR: {errorEvent}");
            break;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What makes this powerful:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Real-time visibility:&lt;/strong&gt; You see exactly what’s happening as the workflow&amp;nbsp;executes&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Type-safe pattern matching:&lt;/strong&gt; Each event type can be handled differently&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Rich context:&lt;/strong&gt; Events carry the full data from each&amp;nbsp;step&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Built-in events:&lt;/strong&gt; WorkflowOutputEvent and WorkflowErrorEvent are provided automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Event-Driven Debugging
&lt;/h2&gt;

&lt;p&gt;When something goes wrong, events tell the&amp;nbsp;story:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fex3eanzkzp9t53p2350y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fex3eanzkzp9t53p2350y.png" alt="Terminal output showing a high-urgency customer email routed through the workflow to human escalation and summarized for review." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry Integration: Distributed Tracing
&lt;/h2&gt;

&lt;p&gt;Beyond custom events, Agent Framework integrates with OpenTelemetry for production-grade observability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up OpenTelemetry
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService("AgentFrameworkWorkflows"))
    .AddSource("Microsoft.Agents.AI.*")
    .SetSampler(new AlwaysOnSampler())
    .AddOtlpExporter(options =&amp;gt;
    {
        options.Endpoint = new Uri("http://localhost:4319");
        options.Protocol = OtlpExportProtocol.Grpc;
    })
    .Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This configuration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Identifies your service&lt;/strong&gt; (AgentFrameworkWorkflows)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Captures Agent Framework traces&lt;/strong&gt; (Microsoft.Agents.AI.*)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Samples all traces&lt;/strong&gt; (use selective sampling in production)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Exports via OTLP&lt;/strong&gt; to your observability backend (Jaeger, Zipkin, Azure Monitor,&amp;nbsp;etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What Gets&amp;nbsp;Traced
&lt;/h3&gt;

&lt;p&gt;OpenTelemetry automatically captures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Executor execution times&lt;/strong&gt;: How long each step&amp;nbsp;takes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent LLM calls:&lt;/strong&gt; Token counts, latencies, model&amp;nbsp;calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State operations:&lt;/strong&gt; Reads and writes to shared&amp;nbsp;state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge transitions:&lt;/strong&gt; Which conditional paths were&amp;nbsp;taken&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error contexts:&lt;/strong&gt; Stack traces with workflow&amp;nbsp;context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Visualizing Workflows with AI&amp;nbsp;Foundry
&lt;/h2&gt;

&lt;p&gt;Here’s where it gets really powerful. The Azure AI Foundry VS Code extension can visualize your workflow execution using OpenTelemetry traces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx72htx3rzdgu575fux9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx72htx3rzdgu575fux9i.png" alt="Microsoft Foundry Workflow Visualizer showing the customer support workflow graph alongside execution trace events and agent properties." width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting up visualization:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Install the Azure AI Foundry extension in VS&amp;nbsp;Code&lt;/li&gt;
&lt;li&gt; Configure OTLP endpoint (default &lt;a href="http://localhost:4319" rel="noopener noreferrer"&gt;http://localhost:4319&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; Run your workflow with OpenTelemetry enabled&lt;/li&gt;
&lt;li&gt; Open the Foundry extension panel to see the live&amp;nbsp;trace&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The extension shows your workflow graph in real-time, highlighting the active executor as it runs and showing the complete path when finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Observability
&lt;/h2&gt;

&lt;p&gt;In production, this same infrastructure feeds standard observability tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Monitor: Native integration with Application Insights&lt;/li&gt;
&lt;li&gt;Jaeger: Open-source distributed tracing&lt;/li&gt;
&lt;li&gt;Zipkin: Lightweight tracing visualization&lt;/li&gt;
&lt;li&gt;Datadog/New Relic: Commercial APM platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All support OTLP, so you can monitor workflows alongside the rest of your infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the&amp;nbsp;Demo
&lt;/h2&gt;

&lt;p&gt;To run the workflow&amp;nbsp;example:&lt;/p&gt;

&lt;p&gt;Configure Azure OpenAI in appsettings.Development.json:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ModelName": "your-model-deployment",
  "Endpoint": "https://your-resource.openai.azure.com/",
  "ApiKey": "your-api-key"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run the&amp;nbsp;project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd AgentFrameworkWorkflows
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The workflow will process the email and show&amp;nbsp;you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preprocessing results (detected PII, order&amp;nbsp;IDs)&lt;/li&gt;
&lt;li&gt;Classification output (category, urgency, sentiment)&lt;/li&gt;
&lt;li&gt;Policy decisions (SLA, compliance notes)&lt;/li&gt;
&lt;li&gt;Routing decision and final&amp;nbsp;output&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Patterns and Best Practices
&lt;/h2&gt;

&lt;h2&gt;
  
  
  When to Use Workflows
&lt;/h2&gt;

&lt;p&gt;Workflows shine when you&amp;nbsp;need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-step processes that involve both AI and business&amp;nbsp;logic&lt;/li&gt;
&lt;li&gt;Conditional routing based on&amp;nbsp;context&lt;/li&gt;
&lt;li&gt;Human-in-the-loop patterns&lt;/li&gt;
&lt;li&gt;Compliance requirements that need deterministic enforcement&lt;/li&gt;
&lt;li&gt;Observability into complex agent interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hybrid&amp;nbsp;Approach
&lt;/h2&gt;

&lt;p&gt;Don’t make everything an agent. The power of workflows is in the combination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use deterministic executors for validation, routing, formatting, compliance checks&lt;/li&gt;
&lt;li&gt;Use agentic executors for classification, reasoning, content generation&lt;/li&gt;
&lt;li&gt;Use shared state to pass data between executors&lt;/li&gt;
&lt;li&gt;Use events for observability and debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep Executors Focused
&lt;/h2&gt;

&lt;p&gt;Each executor should do one thing&amp;nbsp;well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PreprocessEmailExecutor: Clean and detect&amp;nbsp;PII&lt;/li&gt;
&lt;li&gt;EmailIntakeExecutor: Classify the&amp;nbsp;email&lt;/li&gt;
&lt;li&gt;PolicyGateExecutor: Apply business&amp;nbsp;rules&lt;/li&gt;
&lt;li&gt;SupportResponderExecutor: Draft responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This modularity makes testing, debugging, and maintenance significantly easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;Agent Framework workflows transform how we build complex AI systems. By providing graph-based orchestration, conditional routing, and shared state management, they enable a hybrid approach that combines the intelligence of LLMs with the reliability of deterministic logic.&lt;/p&gt;

&lt;p&gt;The customer support email triage workflow we explored demonstrates these concepts in action: preprocessing with rules, classification with AI, routing with business logic, and responses crafted by agents — all working together seamlessly.&lt;/p&gt;

&lt;p&gt;As you build your own workflows, remember: the power isn’t in making everything intelligent — it’s in applying intelligence exactly where it’s needed, and using deterministic logic everywhere else.&lt;/p&gt;

&lt;p&gt;🔗 Explore the complete workflow implementation: &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/master/AgentFrameworkWorkflows" rel="noopener noreferrer"&gt;AgentFrameworkWorkflows on&amp;nbsp;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔍 Start from the beginning: Read &lt;a href="https://dev.to/venyabrodetskiy/getting-started-with-microsoft-agent-framework-25l2"&gt;Getting Started with Microsoft Agent Framework&lt;/a&gt; for the fundamentals&lt;/p&gt;

&lt;p&gt;🤝 Your feedback is invaluable! Feel free to drop comments, ask questions, or share your insights and optimizations. Every contribution helps to enhance our collective knowledge and build a resourceful developer community.&lt;/p&gt;

&lt;p&gt;Happy Coding!&amp;nbsp;🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>agents</category>
      <category>microsoftagentframework</category>
    </item>
    <item>
      <title>Getting Started with Microsoft Agent Framework</title>
      <dc:creator>Venya Brodetskiy</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:38:56 +0000</pubDate>
      <link>https://dev.to/venyabrodetskiy/getting-started-with-microsoft-agent-framework-25l2</link>
      <guid>https://dev.to/venyabrodetskiy/getting-started-with-microsoft-agent-framework-25l2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://medium.com/@venya-brodetskiy/getting-started-with-microsoft-agent-framework-61a1112220f8" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on December 27, 2025.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re building agents in &lt;strong&gt;C#/.NET&lt;/strong&gt; in 2025, the hardest part often isn’t writing prompts — it’s picking a foundation. Should you use Semantic Kernel? AutoGen? The OpenAI / Azure OpenAI SDKs directly? And if you’re in Python, the menu gets even&amp;nbsp;longer.&lt;/p&gt;

&lt;p&gt;Microsoft created &lt;strong&gt;Microsoft Agent Framework (MAF)&lt;/strong&gt; to reduce that “framework roulette”. The idea (based on Microsoft’s own announcement posts) is to unify two things developers kept having to choose&amp;nbsp;between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic Kernel’s enterprise-friendly SDK approach (connectors, platform integration, a stable developer surface)&lt;/li&gt;
&lt;li&gt;AutoGen-style multi-agent orchestration patterns (coordination, handoffs, richer agent-to-agent flows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: one open-source SDK + runtime that’s meant to carry you from local experiments to production systems, without rewriting your agent when you move from “demo” to “deployment”.&lt;/p&gt;

&lt;p&gt;If you want Microsoft’s official framing and roadmap language, read these two&amp;nbsp;posts:&lt;/p&gt;

&lt;p&gt;- &lt;a href="https://devblogs.microsoft.com/semantic-kernel/semantic-kernel-and-microsoft-agent-framework/" rel="noopener noreferrer"&gt;Semantic Kernel team perspective&lt;/a&gt; (including SK support expectations)&lt;/p&gt;

&lt;p&gt;- &lt;a href="https://devblogs.microsoft.com/foundry/introducing-microsoft-agent-framework-the-open-source-engine-for-agentic-ai-apps/" rel="noopener noreferrer"&gt;Foundry announcement&lt;/a&gt; (the “why now” +&amp;nbsp;pillars)&lt;/p&gt;

&lt;p&gt;In this guide, we’ll focus on the basics through runnable demos from my repo: agents that call functions, produce structured output, persist threads, integrate with Azure AI Foundry, and use&amp;nbsp;RAG.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Agent Framework?
&lt;/h2&gt;

&lt;p&gt;Agent Framework is an open-source SDK and runtime for building AI agents and orchestrating them into workflows. Today it’s in &lt;strong&gt;public preview&lt;/strong&gt;, and Microsoft positions it as the successor to Semantic Kernel for agent development — effectively “Semantic Kernel&amp;nbsp;v2”.&lt;/p&gt;

&lt;p&gt;That doesn’t mean Semantic Kernel disappears overnight. Microsoft’s stated intent is to keep supporting Semantic Kernel v1.x for the foreseeable future (critical bug fixes and security fixes, plus some features reaching GA), while most new investment goes into Agent Framework.&lt;/p&gt;

&lt;p&gt;The framework focuses on two core capabilities:&lt;/p&gt;

&lt;p&gt;1. &lt;strong&gt;AI Agents&lt;/strong&gt;: Individual agents that use LLMs to process inputs, call tools, and generate responses&lt;/p&gt;

&lt;p&gt;2. &lt;strong&gt;Workflows&lt;/strong&gt;: Graph-based orchestration that connects multiple agents and functions to perform complex, multi-step tasks&lt;/p&gt;

&lt;p&gt;Why build something new at all? The short version is: agents need more than prompt templates. MAF is designed around enterprise realities — durability for long-running agents, richer observability, better portability/interoperability, and a clearer path from local dev to managed&amp;nbsp;hosting.&lt;/p&gt;

&lt;p&gt;You can learn more in the &lt;a href="https://learn.microsoft.com/en-us/agent-framework/" rel="noopener noreferrer"&gt;official Microsoft Agent Framework documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving in, make sure you&amp;nbsp;have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET 10.0 SDK&lt;/strong&gt; or&amp;nbsp;later&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure OpenAI&lt;/strong&gt; account with:\
– API endpoint\
– API key\
– Model deployment (e.g., gpt-4.1, gpt-5.2)\
– Embedding model deployment (for RAG examples)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most projects require an appsettings.json or appsettings.Development.json file with your Azure OpenAI configuration:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ModelName": "your-model-deployment-name",
  "Endpoint": "https://your-resource.openai.azure.com/",
  "ApiKey": "your-api-key",
  "EmbeddingModel": "your-embedding-model-name"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;: Agent Framework is currently in public preview. APIs may evolve in future releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;p&gt;Before we jump into examples, let’s establish the foundational concepts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AIAgent&lt;/strong&gt;: The core abstraction representing an AI agent. It encapsulates the LLM, instructions, and tools the agent can&amp;nbsp;use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools/Functions&lt;/strong&gt;: Actions your agent can perform — from simple C# methods to complex API calls. Agent Framework uses AIFunctionFactory to convert methods into tools the LLM can call. For a deep dive on function calling, check out &lt;a href="https://platform.openai.com/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI’s function calling&amp;nbsp;guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AgentThread&lt;/strong&gt;: Manages conversation context and history. Think of it as the stateful container for your agent’s interactions with&amp;nbsp;users.&lt;/p&gt;

&lt;p&gt;With these building blocks in mind, let’s see them in&amp;nbsp;action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Simple Agent with Function&amp;nbsp;Calling
&lt;/h2&gt;

&lt;p&gt;Let’s start with the fundamentals: creating an agent that can call&amp;nbsp;tools.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Define a function the agent can call
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
    =&amp;gt; $"The weather in {location} is cloudy with a high of 15°C.";

// Convert the function to a tool
var weatherFunction = AIFunctionFactory.Create(GetWeather);
// Create the agent
var agent = new AzureOpenAIClient(
        new Uri(endpoint),
        new AzureKeyCredential(apiKey))
    .GetChatClient(modelName)
    .CreateAIAgent(
        instructions: "say 'just a second' before answering question",
        tools: [weatherFunction],
        name: "myagent");
// Create a thread for conversation
var thread = agent.GetNewThread();
// Run the agent with streaming
var streamingResponse = agent.RunStreamingAsync(userInput, thread);
await foreach (var chunk in streamingResponse)
{
    Console.Write(chunk);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What’s happening here?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Function Definition&lt;/strong&gt;: We define a simple C# method with [Description] attributes. These descriptions are crucial — they’re sent to the LLM to help it understand when and how to use the&amp;nbsp;tool.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tool Registration&lt;/strong&gt;: AIFunctionFactory.Create() uses reflection to analyze your method’s signature and attributes, then generates the tool schema the LLM expects. This schema includes parameter names, types, and descriptions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Agent Creation&lt;/strong&gt;: CreateAIAgent() is an extension method that wraps the ChatClient and provides agent-specific capabilities. The instructions parameter sets the system prompt that guides the agent’s behavior.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Thread Management&lt;/strong&gt;: GetNewThread() creates a conversation context that maintains message history. Each thread is isolated, allowing you to manage multiple conversations independently.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Streaming Execution&lt;/strong&gt;: RunStreamingAsync() sends the user input to the LLM and streams back responses in chunks. This provides immediate feedback to users instead of waiting for the complete response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Under the Hood: Function Calling&amp;nbsp;Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user asks “What’s the weather in Paris?”, here’s what&amp;nbsp;happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The LLM receives the question and the available tool&amp;nbsp;schemas&lt;/li&gt;
&lt;li&gt; It decides to call GetWeather with parameter location: "Paris"&lt;/li&gt;
&lt;li&gt; Agent Framework intercepts this, executes your C#&amp;nbsp;method&lt;/li&gt;
&lt;li&gt; The result is sent back to the LLM as a “tool&amp;nbsp;message”&lt;/li&gt;
&lt;li&gt; The LLM incorporates the result into its final response: “Just a second… The weather in Paris is cloudy with a high of&amp;nbsp;15°C.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This orchestration happens automatically — you just define functions and the framework handles the&amp;nbsp;rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This&amp;nbsp;Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compared to Semantic Kernel’s plugin registration ceremony, this is dramatically simpler:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Semantic Kernel approach (more verbose)
var kernel = builder.Build();
kernel.ImportPluginFromFunctions("WeatherPlugin",
    new[] { KernelFunctionFactory.CreateFromMethod(GetWeather) });

// Agent Framework approach (concise)
var weatherFunction = AIFunctionFactory.Create(GetWeather);
var agent = chatClient.CreateAIAgent(tools: [weatherFunction]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can find the complete implementation in the &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/master/AgentFramework" rel="noopener noreferrer"&gt;AgentFramework project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: Structured Output
&lt;/h2&gt;

&lt;p&gt;Structured output isn’t unique to Microsoft Agent Framework — it’s a core pattern you’ll want in &lt;em&gt;any&lt;/em&gt; serious agent application.&lt;/p&gt;

&lt;p&gt;The moment your agent needs to do something concrete (create a ticket, call an API, route a request), free-form text becomes a liability. You don’t want to regex an LLM response — you want a contract.&lt;/p&gt;

&lt;p&gt;Why it&amp;nbsp;matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliable downstream code (typed fields, not&amp;nbsp;prose)&lt;/li&gt;
&lt;li&gt;Less prompt wrangling (“valid JSON only” becomes the&amp;nbsp;default)&lt;/li&gt;
&lt;li&gt;Easier testing/evals (assert on&amp;nbsp;fields)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a second, underrated effect: a schema becomes a &lt;em&gt;reasoning scaffold&lt;/em&gt;. Instead of producing a nice-sounding paragraph, the model has to commit to specific slots (category, entities, action items, etc.), which often makes behavior more decisive.&lt;/p&gt;

&lt;p&gt;The tradeoff: if you &lt;em&gt;force&lt;/em&gt; the model to fill fields when the input is missing info, it may hallucinate values just to satisfy the schema. Mitigate by designing for uncertainty (nullable fields, "unknown" values, or explicit instructions like “leave fields null if not stated”). OpenAI calls this out explicitly in their Structured Outputs guide: &lt;a href="https://platform.openai.com/docs/guides/structured-outputs" rel="noopener noreferrer"&gt;https://platform.openai.com/docs/guides/structured-outputs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MAF supports structured output directly: define a C# type describing the output you want, then call RunAsync&amp;lt;T&amp;gt;(). The framework generates a schema from your type, asks the model for a response that matches it, and deserializes the result back into&amp;nbsp;T.&lt;/p&gt;

&lt;p&gt;Here’s how to extract structured information from a meeting transcript:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Define your output structure with nested types
[Description("Structured meeting information")]
public class MeetingAnalysis
{
    [JsonPropertyName("date")]
    public string? Date { get; set; }
    [JsonPropertyName("duration_minutes")]
    public int? DurationMinutes { get; set; }
    [JsonPropertyName("attendees")]
    public List&amp;lt;string&amp;gt;? Attendees { get; set; }
    [JsonPropertyName("decisions")]
    public List&amp;lt;string&amp;gt;? Decisions { get; set; }
    [JsonPropertyName("action_items")]
    public List&amp;lt;ActionItem&amp;gt;? ActionItems { get; set; }
}
public class ActionItem
{
    [JsonPropertyName("assignee")]
    public string? Assignee { get; set; }
    [JsonPropertyName("task")]
    public string? Task { get; set; }
    [JsonPropertyName("due_date")]
    public string? DueDate { get; set; }
}
// Create the agent
var agent = chatClient.CreateAIAgent(
    name: "MeetingAnalyzer",
    instructions: "You are an assistant that extracts structured information from meeting transcripts.");
// Run with structured output - notice the generic type parameter
var response = await agent.RunAsync&amp;lt;MeetingAnalysis&amp;gt;(
    $"Please analyze this meeting transcript and extract key information:\n\n{meetingTranscript}",
    thread
);
// Access type-safe properties - no parsing needed!
Console.WriteLine($"Meeting Date: {response.Result.Date}");
Console.WriteLine($"Duration: {response.Result.DurationMinutes} minutes");
Console.WriteLine($"Attendees: {string.Join(", ", response.Result.Attendees)}");
foreach (var item in response.Result.ActionItems)
{
    Console.WriteLine($"- {item.Assignee}: {item.Task} (due: {item.DueDate})");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;How It Works: JSON Schema Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you call RunAsync&amp;lt;MeetingAnalysis&amp;gt;(), Agent Framework:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Generates a JSON schema&lt;/strong&gt; from your C# class using reflection&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Sends it to the LLM&lt;/strong&gt; as part of the function calling specification&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Constrains the response&lt;/strong&gt; to match your schema&amp;nbsp;exactly&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Deserializes&lt;/strong&gt; the JSON response into your strongly-typed object&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The [Description] attributes on your class help the LLM understand what each field represents, improving extraction accuracy. The [JsonPropertyName] attributes control the JSON property names, which is especially useful when working with LLMs that expect specific naming conventions (like snake_case).&lt;/p&gt;

&lt;p&gt;Check out the full example in the &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/master/AgentFrameworkStructuredOutput" rel="noopener noreferrer"&gt;AgentFrameworkStructuredOutput project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: Thread Persistence
&lt;/h2&gt;

&lt;p&gt;In production scenarios, you often need to save and resume conversations. Agent Framework makes this straightforward with custom storage providers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var agent = chatClient.CreateAIAgent(new ChatClientAgentOptions
{
    Name = "Assistant",
    ChatOptions = new() { Instructions = "You are a helpful assistant." },

    // This is the key: you plug in your own message store.
    // Agent Framework will call it to load/save chat history.
    ChatMessageStoreFactory = ctx =&amp;gt; new FileChatMessageStore(ctx.SerializedState)
});

var threadStore = new FileThreadStore(
    storageDirectory: Path.Combine(Environment.CurrentDirectory, "ThreadStorage"));

AgentThread thread;
if (threadStore.Exists)
{
    // Restore the thread state via the agent (not via the thread itself)
    thread = threadStore.Load(serialized =&amp;gt; agent.DeserializeThread(serialized));

    // Chat history is loaded from the message store (see below)
    await DisplayHistoricalMessagesAsync(thread);
}
else
{
    thread = agent.GetNewThread();
}

// After each user turn, save thread state
threadStore.Save(thread);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;How it works in this demo (two&amp;nbsp;layers)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This project persists two&amp;nbsp;things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Thread state (via thread.Serialize()): this is what lets you resume the same AgentThread object.&lt;/li&gt;
&lt;li&gt; Chat messages (via ChatMessageStore): this is what lets you show conversation history even after a&amp;nbsp;restart.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thread state is saved as JSON by FileThreadStore. Restoring is done by the&amp;nbsp;agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save: threadStore.Save(thread)&lt;/li&gt;
&lt;li&gt;Load: threadStore.Load(serialized =&amp;gt; agent.DeserializeThread(serialized))&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The message store is intentionally small. In FileChatMessageStore, you mainly override 2–3&amp;nbsp;members:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AddMessagesAsync(...) to append and persist&amp;nbsp;messages&lt;/li&gt;
&lt;li&gt;GetMessagesAsync(...) to load&amp;nbsp;messages&lt;/li&gt;
&lt;li&gt;Serialize(...) to persist a stable store id into the thread state (this demo stores a generated thread id, then uses it as a filename)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want Redis/SQL/etc, you just need to create your own implementation of ChatMessageStore&lt;/p&gt;

&lt;p&gt;Explore the complete implementation in the &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/master/AgentFrameworkThreadPersistancy" rel="noopener noreferrer"&gt;AgentFrameworkThreadPersistancy project&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example 4: Azure AI Foundry Integration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/products/ai-foundry" rel="noopener noreferrer"&gt;Microsoft Foundry&lt;/a&gt; (old name: Azure AI Foundry) provides managed agent infrastructure in the cloud. Instead of managing agent lifecycle yourself, you can leverage Foundry’s persistent agents.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Connect to Azure AI Foundry
var credential = new AzureCliCredential();
var client = new PersistentAgentsClient(
    new Uri(projectEndpoint),
    credential
);

// Create or retrieve a managed agent
var agent = await client.CreateAgentAsync(
    model: modelName,
    instructions: "You are a helpful assistant",
    tools: tools
);
// Use the agent like any other
var thread = await client.CreateThreadAsync();
var response = await client.RunAsync(agent.Id, thread.Id, userMessage);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why use&amp;nbsp;Foundry?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-native persistence&lt;/strong&gt;: Agents and threads are automatically stored&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifecycle management&lt;/strong&gt;: No need to handle agent state&amp;nbsp;yourself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure integration&lt;/strong&gt;: Seamless authentication and monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale&lt;/strong&gt;: Leverage Azure’s infrastructure for production workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams building production systems, Foundry removes significant operational overhead.&lt;/p&gt;

&lt;p&gt;See the working example in the &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/main/AgentFrameworkFoundryAgent" rel="noopener noreferrer"&gt;AgentFrameworkFoundryAgent project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 5: RAG with Vector&amp;nbsp;Search
&lt;/h2&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) enhances agents with external knowledge. In this demo, Agent Framework integrates RAG via TextSearchProvider as an AI context provider: the agent can trigger retrieval on demand, and the provider injects relevant snippets into the&amp;nbsp;prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Vector&amp;nbsp;Store
&lt;/h2&gt;

&lt;p&gt;First, define your document schema using vector store attributes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private sealed class SearchRecord
{
    // Embedding dimension must match your model (e.g., text-embedding-3-large is 3072)
    private const int EmbeddingDimensions = 3072;
    [VectorStoreKey]
    public required string SourceId { get; init; }
    [VectorStoreData]
    public string? SourceName { get; init; }
    [VectorStoreData]
    public string? SourceLink { get; init; }
    [VectorStoreData(IsFullTextIndexed = true)]
    public string? Text { get; init; }
    [VectorStoreVector(EmbeddingDimensions)]
    public ReadOnlyMemory&amp;lt;float&amp;gt; TextEmbedding { get; init; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These attributes tell the vector store what’s searchable text, what’s metadata, and which field contains the embedding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating and Populating the Knowledge Base
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var azureOpenAiClient = new AzureOpenAIClient(
    new Uri(endpoint),
    ncew AzureKeyCredential(apiKey));

// In this demo, we use an in-memory vector store and Azure OpenAI embeddings.
var embeddingGenerator = azureOpenAiClient
    .GetEmbeddingClient(embeddingModel)
    .AsIEmbeddingGenerator();
VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions
{
    EmbeddingGenerator = embeddingGenerator
});
// This helper creates the collection and uploads sample documents.
var knowledgeBase = await RagKnowledgeBase.CreateAsync(vectorStore, embeddingGenerator);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Integrating RAG with Your&amp;nbsp;Agent
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Func&amp;lt;string, CancellationToken, Task&amp;lt;IEnumerable&amp;lt;TextSearchProvider.TextSearchResult&amp;gt;&amp;gt;&amp;gt; searchAdapter =
    knowledgeBase.SearchAsync;

TextSearchProviderOptions textSearchOptions = new()
{
    SearchTime = TextSearchProviderOptions.TextSearchBehavior.OnDemandFunctionCalling,
    RecentMessageMemoryLimit = 6,
};

var agent = azureOpenAiClient
    .GetChatClient(modelName)
    .CreateAIAgent(new ChatClientAgentOptions
    {
        Name = "myagent",
        ChatOptions = new ChatOptions
        {
            Instructions = "Say 'just a second' before answering."
        },

        // RAG is wired as context provider (not as a normal tool list).
        AIContextProviderFactory = ctx =&amp;gt; new TextSearchProvider(
            searchAdapter,
            ctx.SerializedState,
            ctx.JsonSerializerOptions,
            textSearchOptions)
    });

var thread = agent.GetNewThread();
var response = await agent.RunAsync("What's your return policy?", thread);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What Happens Under the&amp;nbsp;Hood:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; User query arrives: “What’s your return&amp;nbsp;policy?”&lt;/li&gt;
&lt;li&gt; The provider forms a search query (including a bit of recent chat history).&lt;/li&gt;
&lt;li&gt; The vector store returns the top matches (this demo uses top&amp;nbsp;3).&lt;/li&gt;
&lt;li&gt; The provider injects the retrieved text into the model’s&amp;nbsp;context.&lt;/li&gt;
&lt;li&gt; The model answers grounded in that&amp;nbsp;context.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qde7ax9r5er54g1e9gz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qde7ax9r5er54g1e9gz.png" alt="Terminal demo of a Microsoft Agent Framework RAG agent answering a question about BrightTrail Gear's return policy." width="798" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In production you’d typically swap the in-memory store for a persistent vector backend, but the wiring stays the&amp;nbsp;same.&lt;/p&gt;

&lt;p&gt;Dive into the full RAG implementation in the &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/master/AgentFrameworkRag" rel="noopener noreferrer"&gt;AgentFrameworkRag project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Python&amp;nbsp;Support
&lt;/h2&gt;

&lt;p&gt;Agent Framework isn’t just for C# developers. Microsoft provides Python support, enabling cross-language agent development.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from azure.ai.agents import AIAgent
from azure.ai.openai import AzureOpenAIClient

# Create agent
client = AzureOpenAIClient(endpoint=endpoint, credential=credential)
agent = client.create_agent(
    model=model_name,
    instructions="You are a helpful assistant",
    tools=[weather_tool]
)
# Run the agent
thread = agent.create_thread()
response = agent.run(user_input, thread)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Python API mirrors the C# design, making it easy to work across languages or migrate existing Python projects to Agent Framework.&lt;/p&gt;

&lt;p&gt;Check out the Python example in the &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground/tree/master/AgentFrameworkPython" rel="noopener noreferrer"&gt;AgentFrameworkPython directory&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;We’ve covered the fundamentals — individual agents with various capabilities. But what happens when you need to orchestrate multiple agents and deterministic logic into complex workflows?&lt;/p&gt;

&lt;p&gt;That’s where Agent Framework’s workflow system comes in. Workflows let you build graph-based processes that combine LLM agents with business rules, conditional routing, and shared state management.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/venyabrodetskiy/building-multi-step-workflows-with-agent-framework-4lk8"&gt;the next article&lt;/a&gt;, we’ll explore how to build a real-world customer support email triage system that automatically classifies emails, applies business policies, and routes to either automated responses or human escalation.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;Microsoft Agent Framework represents a significant step forward in AI agent development. By learning from Semantic Kernel and AutoGen, it delivers a cleaner, more intuitive API that accelerates development without sacrificing power.&lt;/p&gt;

&lt;p&gt;Whether you’re building simple chatbots or complex multi-agent systems, Agent Framework provides the building blocks you need — and as the successor to SK and AutoGen, it’s the future direction for Microsoft’s AI agent ecosystem.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Explore the complete demo repository&lt;/strong&gt;: &lt;a href="https://github.com/VenyaBrodetskiy/AgentFrameworkPlayground" rel="noopener noreferrer"&gt;AgentFrameworkPlayground on&amp;nbsp;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🤝 &lt;strong&gt;Your feedback is invaluable!&lt;/strong&gt; Feel free to drop comments, ask questions, or share your insights and optimizations. Every contribution helps to enhance our collective knowledge and build a resourceful developer community.&lt;/p&gt;

&lt;p&gt;Happy Coding!&amp;nbsp;🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>agents</category>
      <category>microsoftagentframework</category>
    </item>
    <item>
      <title>AWS Lambda and NestJS: Communication between services</title>
      <dc:creator>Venya Brodetskiy</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:15:31 +0000</pubDate>
      <link>https://dev.to/venyabrodetskiy/aws-lambda-and-nestjs-communication-between-services-5gh2</link>
      <guid>https://dev.to/venyabrodetskiy/aws-lambda-and-nestjs-communication-between-services-5gh2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published on &lt;a href="https://medium.com/@venya-brodetskiy/aws-lambda-and-nestjs-communication-between-services-3ea026bc463a" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; on November 11, 2023.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this guide, we’ll speak about communication in a distributed system using AWS Lambda and NestJS.&lt;/p&gt;

&lt;p&gt;Building on our &lt;a href="https://dev.to/venyabrodetskiy/local-development-with-aws-lambda-and-nestjs-docker-debugging-and-hot-reload-2k0h"&gt;previous exploration&lt;/a&gt;, where we integrated NestJS with AWS Lambda and set up debugging and hot reload within Docker, this guide takes the next logical step. We’ll use the same foundational setup and enhance it to enable seamless communication between lambdas.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The approach in this guide is inspired by a past project where I used&lt;/em&gt; &lt;a href="https://dapr.io/" rel="noopener noreferrer"&gt;&lt;em&gt;Dapr&lt;/em&gt;&lt;/a&gt; &lt;em&gt;with C# microservices. Dapr simplifies the challenges of building microservices. For example, instead of manually setting up communication between services, Dapr provides straightforward methods to do so. This lets developers concentrate on writing the actual functionality without getting bogged down by the technical details of inter-service communication. This Lambda + NestJS communication strategy is deeply inspired by the simplicity and effectiveness of Dapr.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you’re interested in learning more about Dapr and C#, check out my&lt;/em&gt; &lt;a href="https://github.com/VenyaBrodetskiy/DaprPhoneBook" rel="noopener noreferrer"&gt;&lt;em&gt;other repository&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This guide focuses on synchronous requests, but I’ll cover asynchronous requests with SQS later on in other guide.&lt;/p&gt;

&lt;p&gt;You can find &lt;strong&gt;demo application&lt;/strong&gt; in this repo: &lt;a href="https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo" rel="noopener noreferrer"&gt;https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The demo includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An app configured to work locally as per the &lt;a href="https://dev.to/venyabrodetskiy/local-development-with-aws-lambda-and-nestjs-docker-debugging-and-hot-reload-2k0h"&gt;previous guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Implementation of communication between lambdas to function both locally and post-deployment &lt;em&gt;(the focus of this guide)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;How to add queues (SQS) to your solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the subsequent sections, we’ll break down the steps involved in creating communication between lambdas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Understanding the Service Design for Simplified Lambda Communication&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Building the Lambda Communication service&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enhancing the Lambda Communication Service with Retries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Implementing Lambda Factory to support Local and Cloud Environments&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1. Understanding the Service Design for Simplified Lambda Communication
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Concept&lt;/strong&gt;: The goal is to create a service that encapsulates complex logic, making it as user-friendly and straightforward as Dapr. In essence, this service acts as a bridge, simplifying communication between different components of our system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical Example&lt;/strong&gt;: Let’s illustrate how this service should work with a simple NestJS controller. This controller handles an external request (e.g., from a frontend application or Postman) and communicates with another lambda function to fetch data from a database.&lt;/p&gt;

&lt;p&gt;Here’s the code for our PlanController:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { LambdaCommunicationService } from 'src/core/modules/communication';

@Controller('plan')
export class PlanController {
  private readonly logger = new Logger(PlanController.name);
  constructor(
    private readonly lambdaService: LambdaCommunicationService,
  ) {}

  @Get('/:id')
  public async getPlanById(@Param('id') id: string): Promise&amp;lt;PlanRes&amp;gt; {
    this.logger.log(`Inside ${this.getPlanById.name}, id: ${id}`);

    // service to call other lambda
    const result: PlanRes = await this.lambdaService.invoke&amp;lt;PlanRes&amp;gt;(
      Accessor.Plan,          // name of lambda to be called. `Accessor` is an enum representing different lambda functions
      `/planaccessor/${id}`,  // path of request
      HttpMethod.Get,         // the HTTP method for the request
    );
    return result;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;signature of method lambdaService.invoke :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LambdaCommunicationService.invoke&amp;lt;TResponse&amp;gt;(
  service: string, 
  path: string, 
  httpMethod?: HttpMethod, 
  payload?: object): Promise&amp;lt;TResponse&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;service: Identifies the name of lambda function to be called.&lt;/li&gt;
&lt;li&gt;path: Specifies the request's path.&lt;/li&gt;
&lt;li&gt;httpMethod: The HTTP method for the request, defaulting to GET if not specified.&lt;/li&gt;
&lt;li&gt;payload: Optional object containing data to be sent to the lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Benefit:&lt;/strong&gt; With a service like this, calling other lambdas becomes straightforward. All you need is the function name, the path for the call, the method, and any payload if necessary.&lt;/p&gt;

&lt;p&gt;This design significantly reduces development time, as there’s less need to write and maintain complex code for lambda interactions. It also minimizes errors by standardizing how services communicate, ensuring consistency and reliability. Furthermore, such a service improves code readability and maintainability, making it easier for teams to understand and modify the codebase over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note about Error Handling:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;You might notice the absence of a try-catch block. This is because NestJS has built-in exception filters, which handle errors elegantly. You can learn more about this feature in the&lt;/em&gt; &lt;a href="https://docs.nestjs.com/exception-filters" rel="noopener noreferrer"&gt;&lt;em&gt;NestJS documentation on exception filters&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Additionally, my&lt;/em&gt; &lt;a href="https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo" rel="noopener noreferrer"&gt;&lt;em&gt;Lambda-NestJS Demo repository&lt;/em&gt;&lt;/a&gt; &lt;em&gt;contains a custom implementation of an exception filter, though it’s a topic beyond the scope of this guide.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2. Building the Lambda Communication service
&lt;/h2&gt;

&lt;p&gt;In this part let’s implement the service described in Part 1 :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Injectable()
export class LambdaCommunicationService {
  private readonly logger = new Logger(LambdaCommunicationService.name);
  constructor(private lambdaFactory: LambdaFactory) {}

  public async invoke&amp;lt;TResponse&amp;gt;(
    service: string,
    path: string,
    httpMethod: HttpMethod = HttpMethod.Get,
    payload?: object,
  ): Promise&amp;lt;TResponse&amp;gt; {
    try {
      // get instance of lambda object
      const { lambda, functionName } = this.lambdaFactory.getLambda(service);

      // prepare payload
      const lambdaPayload: ICommunicationPayload = {
        httpMethod: httpMethod,
        path: path,
        body: payload ?? undefined,
        headers: {
          'Content-Type': 'application/json',
        },
      };

      const params: InvokeCommandInput = {
        FunctionName: functionName,
        Payload: JSON.stringify(lambdaPayload),
      };

      this.logger.debug(
        `Inside ${this.invoke.name}. Invoking function: ${params.FunctionName} with payload: ${params.Payload}`,
      );

      // call other lambda using aws-sdk
      const response: InvokeCommandOutput = await lambda.invoke(params);

      // handle the response
      const responsePayload = JSON.parse(Buffer.from(response.Payload).toString());
      if (RetriableStatusCodes.includes(responsePayload.statusCode)) {
        throw new CommunicationException(
          JSON.parse(responsePayload.body),
          responsePayload.statusCode,
        );
      }

      this.logger.debug(
        `Inside ${this.invoke.name}. Lambda invoke response payload: ${JSON.stringify(
          responsePayload,
          null,
          ' ',
        )}`,
      );

      // parse the response to expected type
      if (typeof responsePayload.body === 'string' &amp;amp;&amp;amp; this.isJsonString(responsePayload.body)) {
        const result = JSON.parse(responsePayload.body) as TResponse;
        return result;
      }

      return responsePayload.body as TResponse;
    } catch (error: any) {
      if (error.code === 'ECONNREFUSED')
        throw new CommunicationException(
          `Failed to invoke lambda: ${service}`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      throw error;
    }
  }

  private isJsonString(str: string): boolean {
    try {
      JSON.parse(str);
      return true;
    } catch (e) {
      return false;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now I will break down the code above and explain it one by one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;For the first step&lt;/strong&gt;, we utilize the AWS SDK to instantiate a lambda object. This object is responsible for invoking other lambda functions. Alongside this, we also retrieve the specific function name that we intend to call. The creation and management of this lambda object are efficiently handled by the LambdaFactory. Understanding the workings of LambdaFactory is key to our implementation, and it will be explored in greater detail in the further sections (part 4):&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { lambda, functionName } = this.lambdaFactory.getLambda(service);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Next step&lt;/strong&gt; is to build Lambda Payload and invoke params,invoke lambda:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const lambdaPayload: ICommunicationPayload = {
  ...
};

const params: InvokeCommandInput = {
  ...
};

const response: InvokeCommandOutput = await lambda.invoke(params);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Handling the Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a critical step. We need to understand the internal status code in responsePayload to determine if the invocation was successful:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const responsePayload = JSON.parse(Buffer.from(response.Payload).toString());

if (RetriableStatusCodes.includes(responsePayload.statusCode)) {
  throw new CommunicationException(
    ...
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Parsing the Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The response from the lambda can be an object of type TResponse (defined by the developer) or a string. Here's how we handle it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (typeof responsePayload.body === 'string' &amp;amp;&amp;amp; this.isJsonString(responsePayload.body)) {
  const result = JSON.parse(responsePayload.body) as TResponse;
  return result;
}

return responsePayload.body as TResponse;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This part of the code checks the type of the body in the response and parses it accordingly. The use of TypeScript allows us to cast the response to the type TResponse specified by the developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3. Enhancing the Lambda Communication Service with Retries
&lt;/h2&gt;

&lt;p&gt;In this part, we are adding retry functionality to our Lambda Communication Service. The ability to retry failed requests is a critical feature, particularly for dealing with transient network issues or temporary service unavailability.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note.&lt;/strong&gt; However, it’s essential to carefully select HTTP methods due to their idempotency characteristics. GET and DELETE are primarily targeted for retries because they do not change the state in a way that leads to side effects upon repetition. PUT and PATCH can also be considered for retries as they are designed to be idempotent, ensuring that repeated requests result in the same state. However, caution is advised with POST requests, which typically modify state or create resources, could lead to unintended consequences if retried without proper idempotence handling.&lt;/p&gt;

&lt;p&gt;Retries should be applied for error codes that indicate transient issues or server errors where a repeat request might succeed. Generally, 5xx series errors (e.g., 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout) are suitable candidates for retries, suggesting temporary server-side issues, but retrying on 501 Not Implemented is not advisable as this error indicates a permanent limitation of the server, and subsequent retries are unlikely to succeed. As for 4xx series errors (e.g., 400 Bad Request, 401 Unauthorized, 404 Not Found), they typically indicate client-side problems that are unlikely to be resolved by retrying without changes to the request. However, transient errors like 408 Request Timeout, 423 Locked, and 429 Too Many Requests can be exceptions, where retrying with a backoff strategy might be successful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To integrate this feature, we’ll use the async-retry npm package, which provides a straightforward way to implement retry logic. The invoke method in the Lambda Communication Service is enhanced as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Adding a Retry Parameter&lt;/strong&gt;: The invoke method now includes an additional parameter retries with a default value of 3. This parameter determines the maximum number of retries for a request.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public async invoke&amp;lt;TResponse&amp;gt;(
    service: string,
    path: string,
    httpMethod: HttpMethod = HttpMethod.Get,
    payload?: object,
    retries: number = 3,  // new parameter
  ): Promise&amp;lt;TResponse&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;2. &lt;strong&gt;Conditional Retry Logic:&lt;/strong&gt; We introduce a check to enable retries only for idempotent requests. For other HTTP methods, the effective number of retries is set to zero.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// enable retries only for Retriable Http Methods requests
let effectiveRetries;
if (RetriableHttpMethods.includes(httpMethod)) {
  effectiveRetries = retries;
} else {
  effectiveRetries = 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;3. &lt;strong&gt;Retry Mechanism:&lt;/strong&gt; Using the retry function from the async-retry package, we wrap the actual lambda invocation logic. This function will automatically retry the invocation based on the provided retry conditions.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let responsePayload;
await retry(
  async () =&amp;gt; {
    this.logger.debug(
      `Inside ${this.invoke.name}. Invoking function: ${params.FunctionName} with payload: ${params.Payload}`,
    );

    const response = await lambda.invoke(params);
    responsePayload = JSON.parse(Buffer.from(response.Payload).toString());

    if (RetriableStatusCodes.includes(responsePayload.statusCode)) {
      throw new CommunicationException(
        JSON.parse(responsePayload.body),
        responsePayload.statusCode,
      );
    }
  },
  // retry configuration
  {
    retries: effectiveRetries,
    onRetry: (error) =&amp;gt; {
      error &amp;amp;&amp;amp;
        this.logger.warn(`Error while calling lambda: ${params.FunctionName} with payload: ${
          params.Payload
        }, retrying...
        Error: ${JSON.stringify(error, null, ' ')}`);
    },
  },
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Error Handling During Retries:&lt;/strong&gt; In case of an error during an invocation attempt, an error log is generated. This helps in monitoring and debugging issues related to failed lambda invocations.&lt;/p&gt;

&lt;p&gt;By implementing this retry mechanism, we ensure that our Lambda Communication Service is more robust and can handle transient failures more gracefully, thereby improving the overall reliability of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4. Implementing Lambda Factory to support Local and Cloud Environments
&lt;/h2&gt;

&lt;p&gt;In the previous section, we introduced the LambdaFactory within our Lambda Communication Service.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { lambda, functionName } = this.lambdaFactory.getLambda(service);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let’s explore why it’s important and how to implement it to ensure smooth operation both locally and in the cloud.&lt;/p&gt;

&lt;p&gt;The LambdaFactory role is to abstract the creation and configuration of AWS Lambda instances. This abstraction is crucial because it allows our application to adapt dynamically to different environments — local development or cloud deployment—without altering the core business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Understanding the LambdaFactory Implementation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let’s examine the key components of the LambdaFactory service.&lt;/p&gt;

&lt;p&gt;lambda-factory.service.ts:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
import { Configuration } from 'src/config';

interface ILambdaClient {
  lambda: Lambda;
  functionName: string;
}

@Injectable()
export class LambdaFactory {
  private lambdaInstance: Lambda;

  constructor(private config: Configuration) {}

  public getLambda(service: string): ILambdaClient {
    // get function name and endpoint from configuration
    const { name: functionName, endpoint: endpoint } = this.config.getService(service);

    // for cloud
    if (!this.config.IsOffline) {
      this.lambdaInstance = this.lambdaInstance ?? new Lambda({});

      return {
        lambda: this.lambdaInstance,
        functionName: functionName,
      };
    }

    // for local development
    this.lambdaInstance = new Lambda({
      endpoint: endpoint,
    });

    return {
      lambda: this.lambdaInstance,
      functionName: functionName,
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The getLambda method in the LambdaFactory service is key to configuring the Lambda client instance for either local development or cloud deployment, as determined by the Configuration service. This is crucial for two main reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Local Development:&lt;/strong&gt; For local testing, especially with the Serverless framework, each Lambda function often requires a unique endpoint. Therefore, the method creates a new Lambda object for each call, ensuring accurate local simulation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cloud Deployment:&lt;/strong&gt; In the cloud, Lambda functions are identified by their names, not endpoints. Here, the method optimizes performance by reusing the same Lambda instance for each invocation, following a singleton-like pattern.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach ensures flexibility and consistency across different environments, simplifying development and deployment processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Understanding the Configuration service&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In LambdaFactory we used getService method from Configuration service to fetch specific settings for LambdaCommunicationService, based on defined enums.&lt;/p&gt;

&lt;p&gt;configuration.service.ts:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Injectable()
export class Configuration {
  constructor(private configService: ConfigService) {
    this.validateConfig();
  }

  get IsOffline(): boolean {
    return Boolean(this.configService.get&amp;lt;boolean&amp;gt;('IS_OFFLINE'));
  }

  public getService(service: string): IAcccessorConfig {
    try {
      switch (service) {
        case Accessor.Plan:
          return {
            name: this.configService.getOrThrow&amp;lt;string&amp;gt;('PLANACCESSOR_NAME'),
            endpoint: this.configService.getOrThrow&amp;lt;string&amp;gt;('PLANACCESSOR_ENDPOINT'),
          };
        // case Accessor.OtherAccessor:
        // ...
        default:
          throw new Error(
            `Unknown accessor type. Configuration.service misses accessor: ${service}`,
          );
      }
    } catch (e) {
      throw new Error(e);
    }
  }
  private validateConfig(): void {
    // when running application, this function checks that developer didn't forget to add necessary configs to configuration.service (mostly for enums)

    // Validate accessor configurations
    for (const accessor of Object.values(Accessor)) {
      this.getService(accessor as Accessor);
    }

    // Validate queue configuration not missed
    for (const queue of Object.values(Queue)) {
      this.getQueue(queue as Queue);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will need to do some actions to get benefits from using Configuration service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Define Enums:&lt;/strong&gt; Developers need to create enums for different services to streamline configuration retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain .env File:&lt;/strong&gt; They must set up and update the .env file with all necessary variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Error Handling and Validation:&lt;/strong&gt; The service includes error handling in the getService method and a validateConfig method to ensure all configurations are correct and complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Key Benefits of Using LambdaFactory&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In summary, the LambdaFactory is a powerful pattern that simplifies the management of environment-specific configurations and enhances the robustness of our Lambda Communication Service, making it adaptable to both local and cloud environments. Let’s sum up advantages of this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Environment Agnostic:&lt;/strong&gt; LambdaFactory seamlessly switches between local and cloud configurations, enhancing developer productivity and reducing environment-specific bugs.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Configurational Flexibility:&lt;/strong&gt; By centralizing Lambda configurations, it allows easy updates and maintenance of service configurations without altering the core logic.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enhanced Readability:&lt;/strong&gt; The clear separation of concerns and abstraction of environment-specific details make the code more readable and maintainable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;As we finish this guide on improving communication in AWS Lambda with NestJS, we’ve explored the details of creating a communication system that works well both for local development and when used in the cloud.&lt;/p&gt;

&lt;p&gt;From understanding service design and building a robust Lambda Communication service to implementing a Lambda Factory for environment adaptability, this guide has provided a comprehensive pathway to mastering Lambda communications in a distributed system.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo" rel="noopener noreferrer"&gt;Explore the Demo Repository&lt;/a&gt; with the practical implementation of the concepts discussed.&lt;/p&gt;

&lt;p&gt;🔍 If you’re keen to see where it all began, revisit the first part of this exploration in my article: &lt;a href="https://dev.to/venyabrodetskiy/local-development-with-aws-lambda-and-nestjs-docker-debugging-and-hot-reload-2k0h"&gt;“Local Development with AWS Lambda and NestJS: Docker, Debugging, and Hot Reload”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🤝 Your feedback is invaluable! Feel free to drop comments, ask questions, or share your insights and optimizations. Every contribution helps to enhance our collective knowledge and build a resourceful developer community.&lt;/p&gt;

&lt;p&gt;Happy Coding and looking forward to exploring asynchronous requests with SQS in our upcoming guide! 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>microservices</category>
      <category>nestjs</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Local Development with AWS Lambda and NestJS: Docker Debugging and Hot Reload</title>
      <dc:creator>Venya Brodetskiy</dc:creator>
      <pubDate>Sun, 22 Oct 2023 19:15:06 +0000</pubDate>
      <link>https://dev.to/venyabrodetskiy/local-development-with-aws-lambda-and-nestjs-docker-debugging-and-hot-reload-2k0h</link>
      <guid>https://dev.to/venyabrodetskiy/local-development-with-aws-lambda-and-nestjs-docker-debugging-and-hot-reload-2k0h</guid>
      <description>&lt;p&gt;If you’ve ever worked with C#, Azure Functions, and Visual Studio, you know the comfort. Click here, debug there, drop in Docker with a single click — it’s smooth sailing. Visual Studio does a lot behind the scenes, making our dev life so easy.&lt;/p&gt;

&lt;p&gt;But step into AWS Lambdas and Node.js, and the scene changes. Suddenly, things aren’t handed to you on a silver platter. You’re configuring, tweaking, and piecing together bits to make things click.&lt;/p&gt;

&lt;p&gt;In this guide, we’re diving into that setup. How to integrate NestJS and AWS Lambda, dockerize it, and even get debugging and hot reload to play nicely inside Docker — all to mimic the ease Visual Studio provides for Azure Functions, but in the Node.js and AWS domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Integrate NestJS with Lambda?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt; provides a serverless computing environment, enabling easy scaling and operational cost savings&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NestJS&lt;/strong&gt; offers a progressive environment for developing scalable server-side applications with Node.js, blending various programming paradigms.&lt;/p&gt;

&lt;p&gt;Combining AWS Lambda and NestJS infuses the developer-friendly and flexible environment of NestJS with the serverless, auto-scaling capabilities of AWS Lambda, offering a robust solution for efficient development and deployment.&lt;/p&gt;

&lt;p&gt;Advantages of &lt;strong&gt;Lambda + NestJS&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Scaling:&lt;/strong&gt; NestJS’s effective HTTP request handling merged with Lambda’s effortless scaling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Boosted Developer Productivity:&lt;/strong&gt; Harness NestJS’s developer-friendly aspects in a serverless architecture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimized Costs and Resources:&lt;/strong&gt; Develop using NestJS’s comfortable environment and deploy with Lambda’s resource-effective strategy.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the upcoming sections, let’s break down in the steps involved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Wrapping NestJS with Lambda&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuring Hot Reload with Nodemon&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Setting Up Docker-Compose&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enabling Debugging in VS Code&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also find &lt;strong&gt;demo application&lt;/strong&gt; in this repo: &lt;a href="https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo" rel="noopener noreferrer"&gt;https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In demo you can find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configured app according to this guide&lt;/li&gt;
&lt;li&gt;how to implement communication between lambdas to work both locally and after deploy&lt;/li&gt;
&lt;li&gt;how to add queues (SQS) to your solution&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Wrapping NestJS with Lambda
&lt;/h2&gt;

&lt;p&gt;In this part I will explain how to wrap the NestJS application within an AWS Lambda function. I used &lt;a href="https://docs.nestjs.com/faq/serverless" rel="noopener noreferrer"&gt;guide from NestJS official documentation&lt;/a&gt;, so I will not dive too deep in it here. By link you can also find how NestJS influence on lambda cold start and how to optimize it.&lt;/p&gt;

&lt;p&gt;In my case I use &lt;code&gt;serverless-http&lt;/code&gt; library, which facilitates the translation of the Lambda event to an HTTP request that a framework (like Express, which NestJS sits on top of) integrates with.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main.ts:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;serverless&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serverless-http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LogLevel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;isDev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IS_OFFLINE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;logLevels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDev&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verbose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logLevels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expressApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHttpAdapter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;serverless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expressApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To locally run the Lambda, I’m utilizing the Serverless framework, notable for both emulating Lambda locally and streamlining deployments to AWS.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;serverless.yaml:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notificationaccessor&lt;/span&gt;
&lt;span class="na"&gt;frameworkVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs18.x&lt;/span&gt;

&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;notificationaccessor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist/main.handler&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="c1"&gt;# Incremented to prevent timeouts during debugging&lt;/span&gt;
    &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;httpApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serverless-offline&lt;/span&gt;

&lt;span class="na"&gt;custom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serverless-offline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0'&lt;/span&gt; &lt;span class="c1"&gt;# Vital when operating from within a docker container&lt;/span&gt;
    &lt;span class="na"&gt;httpPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${env:HTTP_PORT, '3002'}&lt;/span&gt;
    &lt;span class="na"&gt;lambdaPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${env:LAMBDA_PORT, '4002'}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;notificationaccessor&lt;/code&gt;is name of function, feel free to substitute it with your preferred function name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;plugins&lt;/code&gt;and &lt;code&gt;custom&lt;/code&gt;: Utilize &lt;code&gt;serverless-offline&lt;/code&gt; to emulate AWS Lambda and API Gateway on your local machine, essential for local development and testing. Ensure the npm package is installed as a dev dependency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Post-completion of this stage, you should be capable of running your app utilizing the &lt;code&gt;serverless offline&lt;/code&gt;command.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Configuring Hot Reload with Nodemon
&lt;/h2&gt;

&lt;p&gt;Adopting Nodemon ensures smooth and efficient local development by automatically restarting application when file changes are detected. Additionally, we’ll be utilizing its features to set the stage for seamless debugging — particularly from within a Docker container.&lt;/p&gt;

&lt;p&gt;The configuration for Nodemon is stored in a JSON file, typically named &lt;code&gt;nodemon.json&lt;/code&gt;. Here’s how it can be set up for our project:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nodemon.json:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.yaml"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nest build &amp;amp;&amp;amp; node --inspect=0.0.0.0 node_modules/serverless/bin/serverless offline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"legacyWatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;exec:&lt;/code&gt; The command to execute each time an observed file is modified.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nest build:&lt;/code&gt; Compiles your NestJS application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node --inspect=0.0.0.0:&lt;/code&gt; Enables the Node.js debugger, binding it to all network interfaces. Crucially, &lt;code&gt;--inspect=0.0.0.0&lt;/code&gt; is essential to permit debugging from inside Docker in forthcoming steps.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node_modules/serverless/bin/serverless offline:&lt;/code&gt; Initiates the Serverless Offline plugin.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;legacyWatch:&lt;/code&gt; A fallback mode which can be useful if native file system events are unreliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To invoke Nodemon with this configuration, adjust your &lt;code&gt;npm run start&lt;/code&gt; script in your &lt;code&gt;package.json&lt;/code&gt; to simply call the &lt;code&gt;nodemon&lt;/code&gt; command. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these settings, each time you modify a TypeScript file within the &lt;code&gt;src&lt;/code&gt; directory, Nodemon will rebuild your NestJS app and restart the Serverless Offline plugin, while keeping the Node.js debugger accessible for Docker-based debugging in upcoming segments.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Setting Up Docker-Compose
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Guide to install docker on your machine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker is hailed in the development realm for its ability to create a consistent environment across all stages of a project, which simplifies debugging and facilitates collaborative development by keeping the software environment consistent among all developers.&lt;/p&gt;

&lt;p&gt;Before we hop into Docker-Compose, let's ensure our Dockerfile is prepared.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Dockerfile:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18.18.2-slim&lt;/span&gt;

&lt;span class="c"&gt;# Installing curl to facilitate health checks if necessary.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install &lt;/span&gt;curl &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package manifests and install dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Copy app source to the container&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this Dockerfile, Node 18 is utilized as the base image, ensuring an LTS version to run our app. Dependencies are installed before copying the source code, optimizing Docker cache utilization during builds.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;docker-compose.yml&lt;/code&gt;, one service is defined as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;notificationaccessor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./accessors/accessor.notification&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9229:9229"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HTTP_PORT=3000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LAMBDA_PORT=4000&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./accessors/accessor.notification:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;notificationacccessor_nodemodules:/app/node_modules&lt;/span&gt;
    &lt;span class="c1"&gt;# healthcheck to warmup the lambda &lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;notificationacccessor_nodemodules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;3000:3000&lt;/code&gt;: Mapping the HTTP port inside Docker to our local to be able to call API Gateway from Postman&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;9229:9229&lt;/code&gt;: Facilitates debugging by mapping the debug port inside Docker to a local port.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Environment Variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HTTP_PORT&lt;/code&gt; and &lt;code&gt;LAMBDA_PORT&lt;/code&gt;: These are correlated with parameters in your &lt;code&gt;serverless.yaml&lt;/code&gt;, ensuring consistency between local and Docker-run environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Volumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./accessors/accessor.notification:/app&lt;/code&gt; maps your local code into the Docker container, enabling hot reload and live debugging&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;notificationacccessor_nodemodules:/app/node_modules&lt;/code&gt; ensures that the installed node_modules are utilized and not overwritten by the local volume, aiding in consistency, and speed during local development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Healthcheck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While optional, a health check to warmup your Lambda can ensure it's primed and ready. Adjust intervals as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Enabling Debugging in VS&amp;nbsp;Code
&lt;/h2&gt;

&lt;p&gt;Now let's configure VS code debugger.&lt;/p&gt;

&lt;p&gt;Inside your project's&amp;nbsp;&lt;code&gt;.vscode&lt;/code&gt; directory, you'll need to create a &lt;code&gt;launch.json&lt;/code&gt; file. This file instructs VS Code on how to manage the startup of your debug environment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;launch.json:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compounds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug Backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Debug Manager.Plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug Accessor.Plan"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug Manager.Plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"localRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/managers/manager.plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remoteRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inspector"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"restart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking down &lt;code&gt;launch.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Compounds&lt;/code&gt;: Compound configurations allow you to group multiple debug configurations for simultaneous (or sequential) launch in VS Code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"name": "Debug Backend"&lt;/code&gt;: Simply a friendly name for the compound configuration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"configurations": [ "Debug Manager.Plan",&amp;nbsp;...]&lt;/code&gt;: An array holding all configurations to be launched. If you have more than a single service, you can add here all of them and attach debugger to each of them in one click&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Configurations&lt;/code&gt;: Array of configurations for attaching the debugger to different containers. Specific points to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"address": "localhost"&lt;/code&gt; and &lt;code&gt;"port": 9229"&lt;/code&gt;: Tell VS Code where to find the debugging session to attach to. When you have more than a single container running, you will need to use unique port for each service&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localRoot&lt;/code&gt;and &lt;code&gt;remoteRoot&lt;/code&gt;: Paths ensuring VS Code maps local code files to their corresponding paths inside the Docker container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"restart": true&lt;/code&gt;: Ensures the debugger will try to reattach if the connection is lost.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to Add Configuration for New&amp;nbsp;Service
&lt;/h2&gt;

&lt;p&gt;When expanding your services, you might need to add new debugging configurations. Simply add a new object inside the &lt;code&gt;configurations&lt;/code&gt; array. Update the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, and &lt;code&gt;localRoot&lt;/code&gt; accordingly, ensuring they correspond to the new service context.&lt;/p&gt;

&lt;p&gt;To group multiple configurations into a single debug instance, simply add them to the &lt;code&gt;configurations&lt;/code&gt; array inside your &lt;code&gt;compound&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attaching the&amp;nbsp;Debugger
&lt;/h2&gt;

&lt;p&gt;After running &lt;code&gt;docker-compose up&lt;/code&gt;, ensure your application is running inside Docker. Now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;VS Code&lt;/strong&gt;, navigate to the &lt;strong&gt;Run and Debug&lt;/strong&gt; sidebar.&lt;/li&gt;
&lt;li&gt;Select the appropriate compound or individual configuration.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Start Debugging&lt;/strong&gt; (or press F5).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If configured correctly, VS Code will attach to the debug session running inside your Docker container. You'll be able to set breakpoints, inspect variables, and utilize all the features of the VS Code debugger - right inside your running container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional Setup: Running Backend with a Button in VS&amp;nbsp;Code
&lt;/h2&gt;

&lt;p&gt;To fully embrace a Visual Studio-like experience in VS Code, particularly running your Docker-encapsulated backend with a single-click functionality, you may also configure VS Code to run your Docker-encapsulated backend with a single click, instead of using &lt;code&gt;docker-compose&lt;/code&gt; commands directly. This is achievable with the help of VS Code tasks defined in &lt;code&gt;tasks.json&lt;/code&gt; within the&amp;nbsp;&lt;code&gt;.vscode&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Here's a concise version of &lt;code&gt;tasks.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start backend with docker-compose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker-compose -f ./docker-compose.yml up"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isBackground"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"problemMatcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will also need to change &lt;code&gt;launch.json&lt;/code&gt;, adding new &lt;code&gt;configuration&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Run backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preLaunchTask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start backend with docker-compose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the above setup, here's how to run your backend with a click:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;VS Code&lt;/strong&gt;, navigate to the &lt;strong&gt;Run and Debug&lt;/strong&gt; sidebar.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Run backend&lt;/strong&gt; configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo8ay80yp7gyg5t6wsjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo8ay80yp7gyg5t6wsjm.png" alt="Run backend with one click" width="799" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on &lt;strong&gt;Start&lt;/strong&gt;(or press F5).&lt;/li&gt;
&lt;li&gt;Now select the appropriate compound or individual configuration for debugging and click &lt;strong&gt;Start Debugging&lt;/strong&gt; to attach debugger. Once debugger is attached, you will see it in logs of docker containers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now your Docker-encapsulated backend should begin to build and run, directly from within VS Code, simplifying the local development process and ensuring a consistent environment among all developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for embarking on this journey through local AWS Lambda development with NestJS, leveraging Docker for consistency and hot reload, and enriching the development process with seamless debugging using VS Code.&lt;/p&gt;

&lt;p&gt;Your local development with AWS Lambda should now be as smooth and comfortable as possible, mimicking the ease experienced in other development environments.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/VenyaBrodetskiy/Lambda-NestJS-Demo" rel="noopener noreferrer"&gt;Explore the Demo Repository&lt;/a&gt; with the practical implementation of the concepts discussed.&lt;/p&gt;

&lt;p&gt;🤝 Your feedback is invaluable! Feel free to drop comments, ask questions, or share your insights and optimizations. Every contribution helps to enhance our collective knowledge and build a resourceful developer community.&lt;/p&gt;

&lt;p&gt;Happy Coding! 🚀&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>nestjs</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
