C# 14 Is Here: Everything You Need to Know (With Code Examples)

Microsoft has shipped C# 14 alongside .NET 10, and it’s packed with quality-of-life improvements that make everyday code cleaner, safer, and more expressive. Let’s walk through every major feature — with fresh, practical examples you can try right now.


1. Extension Members — Properties, Statics, and Operators on Any Type

C# has long supported extension methods. C# 14 takes it much further with the new extension block — you can now add properties, static members, and even operators to types you don’t own.

Imagine you work with strings a lot and want a clean .IsValidEmail check, a .WordCount property, and the ability to repeat a string with *:

public static class StringExtensions
{
extension(string text)
{
// Extension property — use it like: email.IsValidEmail
public bool IsValidEmail => text.Contains('@') && text.Contains('.');
// Extension property — use it like: sentence.WordCount
public int WordCount => text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
extension(string)
{
// Static extension property — use it like: string.Placeholder
public static string Placeholder => "N/A";
// User-defined operator — use it like: "hello" * 3 => "hellohellohello"
public static string operator *(string text, int times) =>
string.Concat(Enumerable.Repeat(text, times));
}
}
// Usage:
string email = "dev@example.com";
Console.WriteLine(email.IsValidEmail); // True
Console.WriteLine("hello world".WordCount); // 2
Console.WriteLine(string.Placeholder); // N/A
Console.WriteLine("ping! " * 3); // ping! ping! ping!

This is a game-changer for library authors — your APIs can now feel like true language primitives.


2. The field Keyword — Say Goodbye to Backing Fields

Every C# developer has written a property with validation that forced them to declare a private backing field. The new field keyword lets the compiler generate it for you.

Here’s a UserProfile class that enforces age and username rules — no manual backing fields needed:

public class UserProfile
{
// Compiler synthesizes the backing field automatically
public string Username
{
get;
set => field = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Username cannot be blank.")
: value.Trim().ToLower();
}
public int Age
{
get;
set => field = (value is < 0 or > 150)
? throw new ArgumentOutOfRangeException(nameof(Age), "Age must be between 0 and 150.")
: value;
}
}
// Usage:
var profile = new UserProfile();
profile.Username = " Alice "; // stored as "alice"
profile.Age = 30; // fine
profile.Age = 200; // throws ArgumentOutOfRangeException

Cleaner classes, less noise — and full control over validation logic without the ceremony.


3. Null-Conditional Assignment — Assign Only When It Makes Sense

The ?. operator has been a null-safety staple for reading values. In C# 14, you can use it on the left side of an assignment too — so the right side only executes when the target isn’t null.

Consider an e-commerce scenario where you conditionally update a shopping cart:

public class Cart
{
public string PromoCode { get; set; }
public List<string> Items { get; set; } = new();
public decimal Discount { get; set; }
}
Cart activeCart = GetActiveCart(); // might return null
// Old way — manual null guard required:
if (activeCart != null)
{
activeCart.PromoCode = "SAVE20";
activeCart.Discount += 20m;
}
// C# 14 — clean and concise:
activeCart?.PromoCode = "SAVE20";
activeCart?.Discount += 20m; // compound assignment works too!
// The promo code lookup won't even run if activeCart is null:
activeCart?.PromoCode = FetchBestPromoFromApi();

Notice the last line — FetchBestPromoFromApi() is not called at all if activeCart is null. That’s a real benefit when those calls are expensive.


4. Implicit Span Conversions — High-Performance Code, Less Friction

Span<T> and ReadOnlySpan<T> are the go-to tools for zero-allocation data processing in .NET. C# 14 introduces implicit conversions so you can pass arrays where spans are expected — and vice versa — without manual casting.

// A method that processes data efficiently using ReadOnlySpan
static double AverageTemperature(ReadOnlySpan<double> readings)
{
double sum = 0;
foreach (var temp in readings) sum += temp;
return sum / readings.Length;
}
double[] dailyReadings = { 22.5, 23.1, 21.8, 24.0, 22.9 };
// C# 14: the array converts implicitly — no .AsSpan() call needed
double avg = AverageTemperature(dailyReadings);
Console.WriteLine($"Average: {avg:F1}°C"); // Average: 22.9°C
// Span and ReadOnlySpan also compose more naturally with generics
ReadOnlySpan<char> greeting = "Hello, World!";
Span<char> buffer = new char[greeting.Length];
greeting.CopyTo(buffer);

Less ceremony around high-performance code means it’s easier to adopt these patterns throughout your codebase.


5. nameof with Unbound Generics — Cleaner Type Names

Previously, nameof required a closed generic type like nameof(Dictionary<string, int>) just to get back "Dictionary". In C# 14, you can pass the unbound form directly.

// Before C# 14 — you had to supply dummy type arguments:
string name1 = nameof(Dictionary<string, int>); // "Dictionary"
string name2 = nameof(List<object>); // "List"
// C# 14 — just use the unbound form:
string name3 = nameof(Dictionary<,>); // "Dictionary"
string name4 = nameof(List<>); // "List"
string name5 = nameof(Func<,,>); // "Func"
// Real-world use: building a generic cache key or log label
public static string CacheKey<T>() => $"cache:{nameof(List<>)}:{typeof(T).Name}";
Console.WriteLine(CacheKey<int>()); // cache:List:Int32
Console.WriteLine(CacheKey<string>()); // cache:List:String

6. Lambda Parameters with Modifiers — No Full Types Required

When a lambda parameter needed a modifier like ref or out, you previously had to write the full type for every parameter, even the ones you didn’t care about. Not anymore.

// A delegate for a try-parse style conversion
delegate bool TryConvert<TIn, TOut>(TIn input, out TOut result);
// Before C# 14 — all parameter types had to be spelled out:
TryConvert<string, int> parseOld = (string text, out int result) =>
int.TryParse(text, out result);
// C# 14 — just add the modifier, skip the type:
TryConvert<string, int> parseInt = (text, out result) => int.TryParse(text, out result);
TryConvert<string, bool> parseBool = (text, out result) => bool.TryParse(text, out result);
// Works great for inline swap utilities with ref params:
delegate void Swapper<T>(ref T a, ref T b);
Swapper<int> swap = (ref a, ref b) => (a, b) = (b, a);
int x = 10, y = 20;
swap(ref x, ref y);
Console.WriteLine($"x={x}, y={y}"); // x=20, y=10

7. Partial Constructors and Events — Better Source Generator Support

Source generators are a cornerstone of modern .NET tooling (think: EF Core, SignalR, logging). C# 14 extends the partial keyword to constructors and events, making generator-heavy classes much easier to split between generated and hand-written code.

// File: OrderProcessor.cs (your hand-written code)
public partial class OrderProcessor
{
// Defining declaration — no body, no initializer
public partial OrderProcessor(string region);
public void Process(Order order) => Console.WriteLine($"Processing in {_region}");
}
// File: OrderProcessor.Generated.cs (source generator output)
public partial class OrderProcessor
{
private readonly string _region;
private readonly ILogger _logger;
// Implementing declaration — has the body and base/this call
public partial OrderProcessor(string region) : base()
{
_region = region;
_logger = LoggerFactory.Create(b => b.AddConsole()).CreateLogger<OrderProcessor>();
_logger.LogInformation("OrderProcessor created for region: {Region}", region);
}
}

Your hand-written file stays clean and readable. The generated file handles all the plumbing. No mismatch, no duplication.


8. User-Defined Compound Assignment Operators

Custom types can now define their own behavior for compound operators like +=, -=, and *=. Previously this was inferred from the binary operator; now you can override it directly for full control.

public struct Budget
{
public decimal Amount { get; private set; }
public Budget(decimal amount) => Amount = amount;
// Standard addition
public static Budget operator +(Budget a, Budget b) => new(a.Amount + b.Amount);
// User-defined compound: += can now enforce a cap rule
public static Budget operator +=(Budget current, Budget extra)
{
var newAmount = current.Amount + extra.Amount;
return new Budget(Math.Min(newAmount, 100_000m)); // cap at 100k
}
public override string ToString() => $"${Amount:N0}";
}
var budget = new Budget(90_000m);
budget += new Budget(15_000m); // would be 105k — capped to 100k
Console.WriteLine(budget); // $100,000

Getting Started with C# 14 Today

All of these features are available now. To try them out:

C# 14 continues the language team’s philosophy of removing friction without adding complexity. Each of these features solves a real daily annoyance — and the cumulative effect on code readability is significant.

Source: What’s new in C# 14 — Microsoft Learn

Python Agents Just Got Smarter: Agent Skills Is Now Officially Production-Ready

Microsoft has officially released Agent Skills for Python as a stable, production-ready API inside the Microsoft Agent Framework. If you’ve been holding off on building agent-powered applications because of experimental APIs, it’s time to take a second look.

What Are Agent Skills?

Agent Skills is an open format for bundling domain expertise — instructions, reference documents, and executable scripts — into reusable packages that agents load on demand. Instead of cramming everything into a single system prompt, an agent advertises available skill names, then progressively loads only what it needs for the current task. The result: a leaner context window and agents that stay fast and focused.

Each skill is described by a SKILL.md file (for file-based skills) or equivalent code properties. The agent moves through four stages: advertise → load instructions → read resources → run scripts — fetching only what’s relevant for the job at hand.

Three Ways to Build Skills

The release supports three authoring styles, all treated identically at runtime:

  • File-based skills — A directory containing a SKILL.md, optional scripts, and supporting documents. Ideal for cross-functional teams maintaining skills in a shared repository.
  • Class-based skills — Python classes that package instructions and scripts, distributable via internal PyPI feeds like any other Python package.
  • Code-defined skills — Skills created directly in application code, useful when a skill must be generated dynamically or needs to close over application state.

Built for Enterprise Use

Production readiness means more than a stable API. This release ships with the governance controls enterprises need:

  • Human-in-the-loop approval — The three core skill tools require explicit approval by default. Selectively relax approval for trusted, read-only operations.
  • Controlled script execution — File-based scripts are delegated to a runner you supply, giving full control over sandboxing, resource limits, and audit logging.
  • Filtering — Expose only a curated subset of a shared skill library to a specific agent, with context-aware predicates based on the requesting agent or tenant.
  • Caching — Skills resolve once and are reused, with optional per-key isolation for multi-tenant scenarios.

Real-World Use Cases

  • Policy enforcement — Package HR policies, expense rules, or IT security guidelines as skills. Agents load the right policy at query time for consistent, grounded answers.
  • Support playbooks — Turn troubleshooting guides into skills so agents follow documented resolution steps every time.
  • Multi-team composition — Teams author and publish skills independently; you assemble them into a single agent with no cross-team coordination required.

Getting Started

from agent_framework import Agent, SkillsProvider
from pathlib import Path

skills_provider = SkillsProvider.from_paths(
    skill_paths=str(Path(__file__).parent / "skills"),
    disable_load_skill_approval=True,
    disable_read_skill_resource_approval=True,
)

async with Agent(client=client, instructions="You are a helpful assistant.",
                 context_providers=[skills_provider]) as agent:
    response = await agent.run("Help me with onboarding.")

Learn More

With the Python API now stable, teams can build on Agent Skills in production without worrying about breaking changes — a solid foundation for shipping governed, composable AI agents at scale.

Source: Microsoft Agent Framework Blog

Deno 2.9: Build Native Desktop Apps, Faster Startup, and a Smarter Toolchain

Deno 2.9 is out, and it’s one of the most feature-packed releases in the runtime’s history. The headline is deno desktop — a new way to ship native desktop applications straight from the JavaScript and TypeScript stack you already use, with no Electron boilerplate and a single binary at the end. But that’s only part of the story. Cold starts are roughly twice as fast, memory use under load has dropped to a third of what it was in 2.8, and migrating an existing npm or pnpm project to Deno is now a matter of a couple of commands.

To upgrade: deno upgrade

deno desktop: Native Apps From Your Web Stack

Building a desktop app has traditionally meant picking up Electron or Tauri, learning a separate toolchain, and shipping an artifact that bears little resemblance to the rest of your codebase. Deno 2.9 changes that with deno desktop.

Point it at any script or web framework project and it produces a native, self-contained desktop application where the UI runs in a webview and your logic runs in Deno. Because deno desktop is built on the same machinery as deno compile, the output is a single distributable binary with your code and assets embedded — no installer wizard, no runtime dependency on the host machine.

The simplest possible app is just a Deno.serve() call — the webview automatically binds to the port the server opens, so there’s no port wiring to configure:

// main.ts
Deno.serve(() =>
new Response(
"<!DOCTYPE html><h1>Hello from Deno desktop!</h1>",
{ headers: { "content-type": "text/html" } },
)
);
// Then run:
// deno desktop main.ts

deno desktop also shares the framework auto-detection introduced in 2.8. Run deno desktop . in a Next.js, Astro, Fresh, Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, or Vite SSR project and it will detect the framework, build it, and wrap the result automatically. Add --hmr for hot module replacement during development.

Native Desktop APIs Built In

Richer applications get a full set of native APIs available immediately under Deno.*, with no extra packages to install:

  • Deno.BrowserWindow — programmatic control over window size, position, visibility, menus, and DevTools. Bridge between the webview and Deno by binding a function with window.bind() and calling it from page JavaScript via the bindings namespace.
  • Deno.Tray — system-tray icons and panels on all platforms.
  • Deno.Dock — macOS Dock integration.
  • Deno.autoUpdate() — a polling auto-updater that applies binary patches in the background.
  • prompt(), alert(), and confirm() render as native OS dialogs.

Webview or Bundled Chromium

Every deno desktop app needs a browser engine to render its UI. You choose with the --backend flag:

  • webview (default) — uses the OS’s built-in engine (WebView2 on Windows, WebKit on macOS and Linux). Nothing extra is bundled, so binaries stay small and start instantly.
  • cef — bundles Chromium via the Chromium Embedded Framework, guaranteeing the same modern rendering engine on every platform. It adds tens of megabytes but ensures identical behaviour across Windows, macOS, and Linux.

Most apps are happy with the default webview; reach for cef when rendering consistency across platforms is non-negotiable.

Cross-Platform Distribution From a Single Machine

Distribution format follows the extension you pass to --output: .app or .dmg on macOS, .exe or .msi on Windows, .AppImage, .deb, or .rpm on Linux. You don’t need a fleet of build machines. --target cross-compiles to any supported platform and --all-targets builds them all in one command — the Windows .msi and Linux .deb/.rpm installers are authored in pure Rust, so they’re produced from any host without platform-specific packaging toolchains.

deno desktop --output MyApp.dmg main.ts # build for the host
deno desktop --target x86_64-pc-windows-msvc main.ts # cross-compile to Windows
deno desktop --all-targets main.ts # build all five targets at once

Note: deno desktop is experimental in 2.9. The API is stabilising and some platform features are still landing. Full documentation is at docs.deno.com/runtime/desktop.

Performance: Twice as Fast, One-Third the Memory

Deno 2.9 ships substantial performance improvements across the board:

  • Cold start: A hello-world program now starts in ~17 ms, down from ~34 ms in 2.8 — nearly 2x faster.
  • Memory: Resident set size under Deno.serve workloads is now essentially flat regardless of payload size — ~62 MB steady-state vs up to 197 MB in 2.8. That’s 3.1x less peak memory on 1 MiB body workloads.
  • HTTP throughput: Deno.serve is 11–27% faster across plaintext, real-world JSON, and large-body workloads, thanks to a new Deno-owned HTTP/1.1 serving path.
  • Crypto and inspect: crypto.subtle and console/Deno.inspect hot paths have been ported from JavaScript to Rust, reducing per-call overhead.

Migrating From npm, pnpm, yarn, or Bun Is Now Trivial

The biggest friction when switching package managers has always been the risk of accidentally upgrading pinned dependencies. Deno 2.9 eliminates that concern entirely. Run deno install in a project that has a package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lock and Deno will seed a fresh deno.lock directly from it, carrying over every resolved version and integrity hash. No re-resolution, no surprise upgrades.

pnpm workspaces, which previously caused confusing resolution errors because their configuration lives in a separate pnpm-workspace.yaml, are now handled automatically: Deno detects the file and migrates its packages, catalog, and catalogs entries into your deno.json without touching your comments or existing fields.

Build tools that shell out to a node binary directly (like Next.js’s Turbopack worker pool) also work without intervention: when no real node is installed, Deno now puts a stand-in on PATH that forwards to itself and translates Node’s CLI arguments. Set DENO_DISABLE_NODE_SHIM=1 to opt out.

A Much Stronger Test Runner

Deno 2.9 closes the gap between the built-in test runner and tools like Vitest and Jest with a wave of new features:

  • Snapshot testing: t.assertSnapshot() is now built directly into the test context, no import required.
  • Change-aware test selection: deno test --changed runs only the tests affected by your uncommitted changes; --changed=origin/main scopes that to a branch diff. Selection walks the full module graph, including across workspace members.
  • Retries and repeats: deno test --retry=2 re-runs failing tests up to two extra times; --repeats=5 runs each test five times and requires all passes. Tests that only pass after a retry are flagged as flaky in the summary.
  • Coverage thresholds: deno coverage --threshold=90 fails the run when line, branch, or function coverage drops below a target, configurable per-metric in deno.json.
  • Sharding: deno test --shard=2/3 splits test files into balanced groups for parallel CI runs.
  • Parameterised tests: Deno.test.each([...]) registers one independently-filterable test per case from a data table, with printf-style name interpolation.

Supply Chain Security Gets Smarter Defaults

Two supply chain guards are now active or available:

  • Minimum dependency age (24 hours, on by default): Deno refuses to install any npm package version published within the last 24 hours. Most malicious packages are detected and unpublished within a day of release, so this single default catches a large class of supply-chain attacks silently. Configurable in .npmrc with min-release-age=72h to wait longer, or min-release-age=0 to opt out entirely.
  • no-downgrade trust policy (opt-in): Enables a provenance-aware trust check that refuses to resolve a package version whose publication trust evidence is weaker than that of any earlier version of the same package — the hallmark of a compromised maintainer token. Enable with trust-policy=no-downgrade in .npmrc.

More Highlights Worth Knowing

  • CSS module imports: import sheet from "./styles.css" with { type: "css" } now works in Deno (under --unstable-raw-imports), returning a CSSStyleSheet instance that runs identically in Deno and in the browser.
  • deno task input-based caching: Declare a task’s files inputs in deno.json and Deno skips the task entirely when nothing relevant has changed, restoring output artifacts from cache.
  • deno link / deno unlink: Manage local package links from the CLI rather than hand-editing config.
  • deno list: A new subcommand that prints declared dependencies and their resolved versions, the equivalent of npm ls.
  • Post-quantum cryptography: crypto.subtle now supports ML-KEM, ML-DSA, SLH-DSA (FIPS 203/204/205), ChaCha20-Poly1305, the SHA-3 family, and Argon2 key derivation.
  • Node.js 26 compatibility: The compatibility target advances to Node 26, and bare node builtins (import "fs") now resolve without any flags.
  • deno watch: A new, more discoverable alias for deno run --watch-hmr.
  • Web Locks API: Full support for navigator.locks for coordinating access to named resources across async tasks and workers.

Getting Started

Upgrade with deno upgrade, or install fresh from deno.com. The full changelog is on GitHub, and the deno desktop documentation is at docs.deno.com/runtime/desktop. For a complete real-world example of a deno desktop app, check out denidian, a note-taking app built with the new feature.

Source: Deno Blog — Deno 2.9 by Bartek Iwańczuk

How the Agent-to-Agent (A2A) Protocol is Reshaping Multi-Agent Collaboration

A year ago, Google introduced the Agent-to-Agent (A2A) protocol — a communication standard designed from the ground up for the era of generative AI. Where traditional APIs are rigid and deterministic, A2A was built for agents: fluid, autonomous systems that need to collaborate, hand off tasks, and maintain secure boundaries without getting in each other’s way. As A2A celebrates its first anniversary, the ecosystem has grown far beyond what most anticipated.

The Problem With Treating Agents Like APIs

If you’ve built with AI agents before, you’ve likely hit the wall of trying to wire them together using conventional REST APIs. It works — until it doesn’t. Standard APIs return data or errors. They can’t ask clarifying questions, refine an ambiguous request, or adapt mid-task. And when you start chaining multiple agents together, context windows overflow, proprietary logic leaks, and the whole system becomes a fragile monolith.

A2A was designed to solve these problems at the architectural level. Here’s how:

1. Secure Boundaries — Protecting Your “Secret Sauce”

Enterprise agents often need to work with sensitive internal data or proprietary business logic that should never be exposed to an external system or a public LLM. A2A enables a clean “black box” handoff: you assign a task to a specialized internal agent, it executes in its own secure environment, and only the high-value output is returned. Your data and how-to logic stay encapsulated and private throughout.

2. Zero Context Pollution

Every LLM has a finite context window. Force a primary agent to manage complex, multi-step dependencies on top of a conversation, and you’ll quickly see hallucinations and degraded output quality. With A2A, specialized peer agents manage their own state and dependencies, handling complexity internally without ever crowding the primary agent’s memory. Each participant keeps its focus.

3. Dynamic Autonomy

An API either returns a result or fails. An A2A peer agent does something fundamentally different: it collaborates. It can interpret intent, ask for clarification when the request is incomplete, push back on ambiguity, and adapt its approach based on intermediate results. This transforms inter-agent communication from a data transfer into a genuine working relationship.

4. Distributed Workloads and Modular Design

Instead of one team building an entire agentic solution end-to-end, A2A enables workload distribution. Different components of a solution can be developed and maintained by separate teams, vendors, or managed agentic services — each a domain expert in their slice. The result is a modular architecture that is easier to build, easier to test, and far easier to evolve over time.

Real-World Spotlight: FoldRun and Protein Structure Prediction at Scale

To understand how A2A works in practice, consider one of the hardest problems in biology: predicting a protein’s 3D structure. It requires petabyte-scale genetic databases, specialized GPU infrastructure, and orchestration across multiple AI models (AlphaFold, OpenFold, Boltz). For a developer, building this from scratch is an enormous undertaking.

FoldRun reimagines this entirely. Rather than a fragile pipeline of glued-together APIs, FoldRun is a self-contained, agentic interface. You add it to Gemini Enterprise, the Gemini CLI, or any A2A-compatible environment, assign a structure prediction task in natural language, and FoldRun takes over — managing long-running autonomous tasks, dynamically choosing between models based on prediction confidence, and delivering results as a specialized peer agent. No custom glue code required.

“Having a solution that allows our scientists to use co-folding models with an agentic interface — one which our organization is embracing through Gemini Enterprise — has made testing and integration with workflows much easier.”

— Richard Hughes, BicycleTx

What Else Is the Ecosystem Building?

The A2A community has expanded well beyond life sciences. Here’s a snapshot of where developers are taking the protocol:

  • Agentic Commerce and Autonomous Payments: AI agents are being used to negotiate deals, verify inventory, and execute B2B transactions on behalf of users — with A2A providing the transactional integrity layer.
  • Enterprise Data and Real-Time Streaming: Specialized A2A agents sit at the edge of live event streams and databases, extracting insights and triggering downstream workflows only when specific, compliance-approved conditions are met — without ever exposing raw data to a central model.
  • Cross-Platform IT and DevOps: Operational silos are dissolving. An HR agent can now hand off provisioning parameters to a DevOps peer agent via A2A, which then autonomously configures software licenses, repository access, and secure environments across disconnected SaaS platforms.
  • Secure Telecom and Regulated Networks: In sectors where data exposure is not an option, A2A is being used to implement quantum-safe, end-to-end Message Layer Security (MLS) for autonomous systems — enabling agent collaboration on sensitive data without any underlying information escaping the secure channel.

How to Get Started

The official A2A SDKs are the fastest on-ramp to the ecosystem:

  • Python and Go: Version 1.0 GA — stable and production-ready
  • Java: Beta, tracking the 1.0 spec
  • .NET: Preview, on track for GA
  • JavaScript / TypeScript: Stable on v0.3; 1.0 work in progress

Whether you are building an agent from scratch or extending an existing system to interoperate with the ecosystem, the path to A2A compliance has never been more straightforward.

The Bigger Picture

The shift A2A represents is more than a new protocol. It’s a move away from AI as a monolith — one massive agent trying to do everything — toward AI as an ecosystem: a network of specialized, collaborating agents, each excellent at its job, each secure in its own domain, each able to hand off and receive work through a common language. One year in, that vision is no longer theoretical. It’s shipping code.

Source: Google for Developers Blog — How A2A is Building a World of Collaborative Agents by Alan Blount, Frank Guan, and Nick Losier

Building Intelligent Agents with Microsoft Agent Framework: The Harness and Claw Explained

What if you could build a fully functional AI agent — one that plans, searches the web, calls your own custom tools, and remembers context across sessions — without wiring together a dozen different libraries? That’s the promise of Microsoft Agent Framework’s harness model, and this post walks through exactly how it works.

This is Part 1 of the Build Your Own Claw series. The guiding example is a personal finance assistant, and by the end you’ll have an interactive terminal agent that can look up stock prices, pull in live market news, and build a step-by-step investment plan on request.

What Is a “Harness” and What Is a “Claw”?

The terminology is deliberately visual. A claw is an agent loop — the thing that wraps a language model, connects it to tools, and keeps the conversation going. A harness is the scaffolding Microsoft Agent Framework provides around that loop: function invocation, conversation history, planning, web search, and file memory are all bundled in by default. You supply the two things that make your agent unique — its purpose (instructions) and its domain-specific tools — and the framework handles everything else.

Step 1: Connect to a Model

Every agent starts with a chat client — the component that communicates with an underlying language model. The framework treats this as a standard interface (IChatClient in .NET), so you can point it at Microsoft Foundry, Azure OpenAI, OpenAI, Anthropic, Google Gemini, Ollama, or any other supported provider without changing the agent code.

For the finance assistant, the setup reads two environment variables — FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL — and authenticates using Azure’s DefaultAzureCredential. Run az login locally and it just works; in production, swap in a ManagedIdentityCredential for tighter security.

The key insight here: the harness is provider-agnostic. Switching from Foundry to OpenAI is a one-line change in the client setup — the rest of the agent stays the same.

Step 2: Wrap the Client in the Harness

This is where the framework’s value becomes obvious. A single call — AsHarnessAgent() in .NET or create_harness_agent() in Python — transforms the bare chat client into a full agent. You pass in two things:

  • Instructions — a natural language description of what the agent does and how it behaves. For the finance assistant this includes guidance on always verifying numbers with tools, citing sources, and maintaining a watchlist in a watchlist.md memory file.
  • A custom tool — a plain function (get_stock_price) that the model can invoke when it needs live data. The framework automatically generates the JSON schema from the function’s signature and parameter descriptions.

In return, the harness activates everything else automatically:

  • Web search is added as a hosted tool — ask “Any recent news on NVDA?” and it works out of the box, no extra code required.
  • Planning is enabled via a built-in TodoProvider and AgentModeProvider — so a vague request like “Review my watchlist and recommend some stocks to add” becomes a structured, step-by-step plan.
  • File memory is wired up so the agent can persist information (like your watchlist) across sessions.
  • History persistence is handled per service call.

Nothing about web search or planning required any custom implementation — those capabilities came free the moment the harness was created.

Step 3: Run the Interactive Console

Microsoft Agent Framework ships a ready-made terminal UI — the harness console — designed to be copied and adapted as a starting point for any interface (web app, chat surface, IDE extension, etc.). It outputs in colour by mode: cyan for planning, green for execution, and includes built-in commands like /todos, /mode, and /exit.

A typical interactive session with the finance assistant might look like:

  1. Switch to execute mode: /mode execute
  2. Ask for a price: “What’s the price of MSFT?” — the agent calls get_stock_price
  3. Ask for news: “Any recent news on NVDA?” — the agent uses web search
  4. Build a watchlist: “Add MSFT, NVDA and SPY to my watch list” — saved to watchlist.md
  5. Switch to plan mode: /mode plan
  6. Request analysis: “Review my watchlist and recommend some stocks to add” — the agent plans, asks clarifying questions, then executes

Sessions can be saved to disk with /session-export and restored later with /session-import, preserving conversation history, the watchlist, and all context-provider state.

How Plan Mode Actually Works

Plan mode isn’t magic — it’s structured output. When the agent is in plan mode, the console’s planning observer overrides the response format to request a JSON schema-constrained reply instead of free-form text. The model is forced into one of exactly two shapes:

  • Clarification — the model needs more information. It returns one or more questions, each optionally accompanied by suggested choices that the console renders as selectable options.
  • Approval — the model has a complete plan. It returns a single summary and the console prompts you to approve before anything executes.

This design makes agent behaviour feel deliberate and safe: the agent gathers what it needs, presents a plan, and only switches to execute mode after you sign off. The PlanningResponse schema ships with the console sample in both .NET and Python, so you can extend or reshape it to match your own UX requirements.

Toggling Features On and Off

Everything the harness enables — todos, planning modes, web search, file memory, file access, and tool approval — is on by default and individually toggleable. If your use case doesn’t need planning or web search, you disable them with a single option flag:

  • .NET: DisableTodoProvider, DisableAgentModeProvider, DisableWebSearch, DisableFileMemory, DisableFileAccess, DisableToolApproval
  • Python: disable_todo, disable_mode, disable_memory, disable_web_search

The recommended approach is to start with everything enabled and trim to taste once you understand what your agent actually needs.

You Don’t Have to Use the Full Harness

The harness is a convenience layer, not a mandatory container. All of the underlying pieces — web search (a plain tool), planning modes (a context provider), and todos (another context provider) — are individually accessible. You can cherry-pick exactly what you need and plug them into any agent architecture, even one that doesn’t use the harness at all. In .NET, the mode and todo providers live in the Microsoft.Agents.AI package; in Python, everything ships in the agent-framework package.

Try It Yourself

Both the .NET and Python runnable samples are available on GitHub:

What Comes Next

The finance assistant can now look things up, search the web, and produce structured plans. But it can’t touch your files directly, and there’s nothing yet preventing it from taking a sensitive action without asking. Part 2 of the series addresses both: granting file access, gating risky operations behind explicit approvals, and adding durable memory so the agent remembers your preferences between sessions.

Source: Microsoft Dev Blogs — Meet your agent harness and claw by Wes Steyn, Principal Software Engineer

LangChain Deep Agents: The Easiest Way to Build Reliable AI Agents

Building AI agents that can handle complex, multi-step tasks has never been easier. LangChain’s Deep Agents is a powerful agent harness that brings together everything you need to build reliable, production-ready LLM-powered agents — all in one package.

What Is Deep Agents?

Deep Agents is a standalone Python library built on top of LangChain’s core building blocks. It uses the LangGraph runtime for durable execution, streaming, human-in-the-loop interactions, and more. Think of it as an “agent harness” — the same core tool-calling loop as other agent frameworks, but with built-in capabilities that make agents reliable for real-world tasks.

Whether you’re building a coding assistant, a data analyst, or a research agent, Deep Agents gives you the infrastructure to do it right from day one.

Quickstart

Getting started is straightforward. Install the library and create your first agent in just a few lines:

# pip install -qU deepagents langchain-google-genai
from deepagents import create_deep_agent
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_deep_agent(
model="google_genai:gemini-3.5-flash",
tools=[get_weather],
system_prompt="You are a helpful assistant",
)
# Run the agent
agent.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)

Core Capabilities

Deep Agents comes with six major built-in capabilities:

  • Take actions in an environment — Invoke tools, read and write files, and execute code.
  • Connect to your data — Load memories, skills, and domain knowledge at the right moment.
  • Manage growing context — Summarize history and offload large results across long runs.
  • Parallelize tasks — Delegate to general or specialized subagents running in isolated context windows.
  • Stay in the loop — Pause for human approval at critical decision points.
  • Improve over time — Update memory, skills, and prompts based on real usage.

Execution Environment

Tools and MCP Support

Pass custom functions, LangChain tools, or tools from any MCP (Model Context Protocol) server using the tools= parameter. Deep Agents fully support MCP, letting you connect to databases, APIs, file systems, and more through a standard interface.

from deepagents import create_deep_agent
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[search, fetch_page, run_query],
)

Virtual Filesystem

The harness provides a configurable virtual filesystem backed by pluggable backends — in-memory state, local disk, LangGraph store, or custom backends. It supports operations like ls, read_file, write_file, edit_file, glob, and grep.

Filesystem Permissions

Declarative permission rules control which files and directories the agent can read or write. You can restrict agents to specific directories, protect sensitive files like .env, and give subagents narrower access than the parent agent.

Code Execution

Deep Agents supports two modes of code execution:

  • Sandbox backends — Expose a shell execute tool for isolated command execution. Ideal for installing dependencies, running tests, or calling CLIs.
  • Interpreters — Add an eval tool running JavaScript in a scoped QuickJS runtime. Great for lightweight data transformations and programmatic tool calling.

Context Management

Skills

Skills package specialized workflows, domain knowledge, and custom instructions for your agent. They follow the Agent Skills standard and use progressive disclosure — the agent reads skill frontmatter at startup and only loads full skill content when a task needs it, keeping startup context compact.

Memory

Memory gives your agent persistent context across conversations — coding style, preferences, conventions, and project guidelines. Memory uses AGENTS.md files and can be updated based on interactions, so preferences carry forward without restating them each session.

Summarization and Context Offloading

The harness automatically compresses conversation history and large intermediate results, isolates subagent work, and uses long-term storage to carry information across threads — all to support multi-step tasks that exceed a single context window.

Prompt Caching

For Anthropic models, Deep Agents automatically applies prompt caching to static sections of the system prompt — base agent instructions, memory, and skill content. This reduces both latency and cost on long-running agents, with no configuration required.

Delegation: Task Planning and Subagents

Deep Agents includes a built-in write_todos tool for structured task tracking with status states (pending, in_progress, completed), giving agents a lightweight planning layer for long-running work.

The subagent system allows the main agent to spin up ephemeral child agents for isolated or parallel tasks. Each subagent gets fresh context, runs autonomously to completion, and returns a single final report — keeping the parent agent’s context clean and token-efficient.

Human-in-the-Loop

Deep Agents integrates with LangGraph interrupts so you can pause for human approval on sensitive tool calls. Use the interrupt_on parameter to specify which tools require a checkpoint:

agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[edit_file, deploy],
interrupt_on={"edit_file": True}, # Pause before every file edit
)

This gives you a runtime safety layer for destructive operations, expensive API calls, and interactive debugging.

Observability with LangSmith

Deep Agents integrates seamlessly with LangSmith for tracing requests, debugging agent behavior, and evaluating outputs. When you’re ready to move to production, LangSmith provides full deployment and monitoring options.

Getting Started

Deep Agents is the right choice if you want a batteries-included agent framework that handles the hard parts — context management, memory, subagent delegation, and human oversight — so you can focus on building what matters.

Source: LangChain Deep Agents Documentation

Statistics and Data Analysis Terminnologies-I

Variance

Variance in mathematics is a measure of how spread out the values in a set of data are. It quantifies the average squared deviation of each data point from the mean (average) of the dataset. In simpler terms, it tells us how far individual data points are from the center of the data.

The formula for variance is:

Where:

Higher variance means the data points are more spread out, while lower variance indicates they are closer to the mean.

Standard Deviation

Standard deviation tells us how much data points tend to deviate from the mean on average. A small standard deviation indicates that the data points are clustered closely around the mean, whereas a large standard deviation shows that the data points are spread out.

Example

Alright, let’s walk through an example to make the concept of standard deviation clearer!

Imagine you have the test scores of five students in a math exam: 80, 85, 90, 95, and 100. Here’s how we calculate the standard deviation step by step:

Step 1: Find the Mean

The mean ((\mu)) is the average of the data:
$$\mu = \frac{80 + 85 + 90 + 95 + 100}{5} = 90$$

Step 2: Calculate Deviations from the Mean

Subtract the mean from each score to find the deviations:

  • (80 – 90 = -10)
  • (85 – 90 = -5)
  • (90 – 90 = 0)
  • (95 – 90 = 5)
  • (100 – 90 = 10)

Step 3: Square Each Deviation

Square the deviations to eliminate negative values:


Step 4: Find the Average of the Squared Deviations

Add up the squared deviations and divide by the total number of data points ((n = 5)):
$$\text{Variance} (\sigma^2) = \frac{100 + 25 + 0 + 25 + 100}{5} = 50$$

Step 5: Take the Square Root of the Variance

The square root of the variance gives us the standard deviation:
$$\sigma = \sqrt{50} \approx 7.07$$

Final Result:

The standard deviation is approximately 7.07. This means that, on average, the test scores deviate from the mean by about 7.07 points.

Now, with this example, you see how the standard deviation helps visualize the spread of scores

Moving Averages

The moving averages method is a statistical technique used to analyze time-series data by smoothing out short-term fluctuations and highlighting trends or patterns over time. It’s often applied in fields like finance, economics, and sales forecasting.

Here’s how it works:

  1. Determine the Window: Choose the number of data points (known as the “window”) you want to consider for calculating the average. For example, you might decide on a 3-month or 5-day moving average.
  2. Calculate the Average: For each period, calculate the average of the chosen number of data points. This average is the moving average for that period.
  3. Shift the Window: Move the window one period forward (e.g., from days 1–5 to days 2–6) and calculate the next average.
  4. Repeat: Continue shifting the window and calculating averages across the entire dataset.

The result is a new series of data (the moving averages), which smooths out random variations in the original data, allowing you to better understand trends.

There are different types of moving averages:

  • Simple Moving Average (SMA): Equal weight is given to all data points in the window.
  • Weighted Moving Average (WMA): More weight is given to recent data points.
  • Exponential Moving Average (EMA): Uses a formula to give progressively more weight to recent data points.
sample c# code for moving averages
// Simple moving average calculation 
double[] data = { 10, 12, 14, 16, 18, 20, 22, 24, 26, 28 }; 
int windowSize = 3; 
double[] movingAverages = new double[data.Length - windowSize + 1]; 
for (int i = 0; i < movingAverages.Length; i++) { 
              movingAverages[i] = data.Skip(i).Take(windowSize).Average(); 
} 
Console.WriteLine("Moving Averages: " + string.Join(", ", movingAverages));
Explanation

Sure! Let me break down the code for you step by step:

Purpose

This code calculates the Simple Moving Average (SMA) of an array of numbers, given a specified window size.


Variables and Initialization

  1. double[] data:
    This array holds the input data (in this case, {10, 12, 14, 16, 18, 20, 22, 24, 26, 28}), which represents a time-series or sequential dataset.
  2. int windowSize:
    This is the size of the moving average window, set to 3. It determines how many consecutive numbers are used to calculate each average.
  3. double[] movingAverages:
    This array will store the computed moving averages. Its length is:
    $$\text{Length of data array} – \text{windowSize} + 1$$
    This ensures that the moving average calculation stops when the remaining data is insufficient to fill the window.

Loop Logic

The for loop iterates through the data array to calculate the moving averages:

  1. Iteration:
    The loop runs from i = 0 to i < movingAverages.Length. During each iteration, it computes the average of a specific slice (sub-array) of the input data.
  2. data.Skip(i).Take(windowSize):
  • Skip(i): Skips the first i elements of the data array.
  • Take(windowSize): Takes the next windowSize elements starting from the current position.
    For example:
  • At i = 0: Takes {10, 12, 14}
  • At i = 1: Takes {12, 14, 16}
  • At i = 2: Takes {14, 16, 18}, and so on.
  1. .Average():
    Computes the average of the selected windowSize elements and stores it in the movingAverages[i] array.

Output

After the loop finishes, all the calculated moving averages are stored in the movingAverages array. Finally, the code prints them using:

Console.WriteLine("Moving Averages: " + string.Join(", ", movingAverages));

This will output the moving averages as a comma-separated list.


Example

For the given data array and windowSize = 3:

  • Moving average for {10, 12, 14} = ((10 + 12 + 14) / 3 = 12)
  • Moving average for {12, 14, 16} = ((12 + 14 + 16) / 3 = 14)
  • Moving average for {14, 16, 18} = ((14 + 16 + 18) / 3 = 16)
  • And so on.

The final output will be:

Moving Averages: 12, 14, 16, 18, 20, 22, 24, 26

The Skip(i) function is used to shift the starting position of the window when calculating moving averages. Here’s why it’s necessary:

  • In the first iteration (i = 0), we want to start the window at the beginning of the data array, which includes the first three elements (e.g., {10, 12, 14}).
  • In the next iteration (i = 1), we want the window to move forward by one position, so it starts at the second element and includes the next three elements (e.g., {12, 14, 16}).
  • This shifting process continues for each subsequent iteration, ensuring that each moving average is calculated using the right set of consecutive numbers.

Without Skip(i), the function would always start from the beginning of the array, and you’d end up calculating the same average repeatedly instead of progressively shifting the window. By skipping i elements, the window moves forward as intended, covering all possible sets of data points.

In the first iteration ((i = 0)), nothing is skipped because (Skip(0)) means “skip zero elements”—essentially, it starts at the beginning of the array, so it includes 10, 12, and 14 as intended.

The confusion might stem from interpreting Skip(i) too literally. Here’s what happens step by step:

  • At (i = 0), data.Skip(0) takes the array as-is (no skipping), so it starts with {10, 12, 14}.
  • At (i = 1), data.Skip(1) skips the first element (10), resulting in {12, 14, 16} being taken.
  • At (i = 2), data.Skip(2) skips the first two elements (10, 12), resulting in {14, 16, 18} being taken.

The Skip(i) ensures the window shifts correctly as the loop progresses. But in the first iteration, nothing is skipped, and 10 is included in the moving average calculation.

Convolution

What is Convolution?

Convolution is a mathematical operation that combines two sequences (arrays) to produce a new sequence. It essentially slides one sequence (called the “kernel” or “filter”) across another sequence (the “input data”) and calculates weighted sums at each step.

Simple Example

Let’s use a very basic case:

Input Data:

Imagine you have the sequence:

Kernel (Filter):

The kernel is a smaller sequence:

How Convolution Works:

The kernel slides across the input data. At each position, we multiply the kernel values by the corresponding input values and sum them up.


Step-by-Step Calculation

Step 1: First Position

Align the kernel with the first three values of the input:
[ [1, 2, 3] ]
Multiply each input value by the corresponding kernel value:

So, the first result is -2.


Step 2: Second Position

Move the kernel one step to the right:
[ [2, 3, 4] ]
Multiply and sum:

The second result is -2.


Step 3: Third Position

Move the kernel again:
[ [3, 4, 5] ]
Multiply and sum:

The third result is -2.


Final Result:

The output of the convolution is:
[ [-2, -2, -2] ]


Why Is This Useful?

Convolution is used in many fields:

  • Moving Averages: To smooth data or detect trends.
  • Image Processing: To apply filters like blurring or edge detection.
  • Machine Learning: In convolutional neural networks for feature extraction.

Basics of Machine Learning

What is a scalar ?

A scalar is also called a zero Dimensional Array . any single number or value is a scalar
example:

Weight: 70 kg

Temperature: 36.6°C

what is a vector ?

A vector is a 1-D array or an array which in most progrmming languages is written as [1,3,4,5]
For example:

A 3D point in space: [2, 5, 7] (x, y, z coordinates)

what is a matrix ?

A matrix is a 2-D array . i.e for e.g a table with rows and columns is a 2D array . It is also called an array inside an array i.e [ [ 1,2,3,4],[7,8,4,3] ]
A matrix is organized in rows and columns. It’s like a grid where each entry corresponds to a specific row and column. For instance:

3D Array..NDarray

A 3D array adds another dimension to the grid. Imagine stacking multiple 2D arrays like slices in a cube. .The same concept applies to higher order arrays like 4D , 5 D arrays . i.e a 4D array is nothing but stacked 3D arrays . Refer to the other blog also about array indexing How elements are indexed in a 3Dimensional array
For instance: the following is the example of a 3D array.
import numpy as np

# Create a 3D array
array = np.array([[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]]])

# Access element at index (1, 0, 2)
element = array[1, 0, 2] # Result: 9
print(element)
# prints 9

Access Element in a 3 Dimensional Array using numpy

import numpy as np

# Create a 3D array
array = np.array([[[1, 2, 3], [4, 5, 6]],
                  [[7, 8, 9], [10, 11, 12]]])

# Access element 9
# Explanation : consider the above 3D array  as a stacked 2D array with 2 layers 
layer 0 :
[[1, 2, 3], [4, 5, 6]]
layer 1 :
[[7, 8, 9], [10, 11, 12]]
9 is in layer 1 
Within that layer, pick the first row.
Then, take the third element in that row
element = array[1, 0, 2]
print(element)  # Output: 9

This structure makes it easy to navigate, slice, and manipulate data in three-dimensional space.

Deploy Flask Web App- Set up Apache as a reverse proxy to Gunicorn

Deploying a Flask app to a Fedora Linux server involves several steps to ensure your app runs smoothly and securely. Here’s a step-by-step guide:

Step-by-Step Guide to Deploy a Flask App to Fedora Linux Server

  1. Set Up Your Fedora Server: Ensure your server is up-to-date and has Python installed. You can update your server and install Python with the following commands: sudo dnf update sudo dnf install python3 python3-venv
  2. Create a Virtual Environment: Set up a virtual environment to manage your project’s dependencies: python3 -m venv myenv source myenv/bin/activate
  3. Install Flask and Gunicorn: Install Flask and Gunicorn within your virtual environment: pip install Flask gunicorn
  4. Create Your Flask App: Develop your Flask application and save it in a directory (e.g., myapp). Here’s a simple example (app.py):
    from flask import

    Flask app = Flask(__name__)

    @app.route('/')
    def home():
    return "Hello, World!"

    if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000)
  5. Test Your Flask App Locally: Before deploying, test your app locally to ensure it works: python app.py Access your app at http://localhost:5000.
  6. Set Up Gunicorn: Configure Gunicorn to serve your Flask app:

    gunicorn --bind 127.0.0.1:5000 app:app

    Replace app:app with module_name:class_name if your app is structured differently.
    module_name is the python file which will act as the entry point for the web app in this example it is app.py so the module_name is app
    class_name : is the class that references a flask instance . In this example you can see
    Flask app = Flask(__name__) ” , so the class_name is app .
  7. Deploy Your App: Transfer your Flask app to the server and run it with Gunicorn:
    if your source code is checked in to git , then use the following , else copy the source files to a directory on a server . The following is the example with git .
    git clone https://your_repository_url.git
    cd your_repository_directory
    #activate virtual environment
    source myenv/bin/activate
    # run the app to verify if gunicorn is serving the web app by doing the following :
    sudo gunicorn --bind 127.0.0.1:5000 app:app
    Open your web browser and navigate to http://localhost:5000. to see your Flask app running on the Fedora server.

Now in production deployments we need to ensure that the app is accessible through a url , so we can do that by setting up a reverse proxy . This setup uses Apache as a reverse proxy and Gunicorn as the WSGI server to serve your Flask app. The steps are described below :

Using Apache as a Reverse Proxy to Gunicorn in Fedora Linux(same applies for other distros too)

Create a system d service

Create a system d service for gunicorn so that it runs continuously in the back ground listening to the port of your web app .
For example Create a systemd service file for Gunicorn, for example, /etc/systemd/system/myapp.service

Example systemd Service File for Gunicorn:

```ini
[Unit]
Description=Gunicorn instance to serve my Flask app
After=network.target

[Service]
User=flaskuser
Group=flaskgroup
WorkingDirectory=/var/www/myproject
Environment="PATH=/var/www/myproject/myenv/bin"
ExecStart=/var/www/myproject/myenv/bin/gunicorn --workers 3 --bind 127.0.0.1:5000 app:app

[Install]
WantedBy=multi-user.target
```
Reload systemd and Start the Gunicorn Service:
sudo systemctl daemon-reload
sudo systemctl start myapp.service
sudo systemctl enable myapp.service

Configure Reverse Proxy in Apache web server

  1. Install Apache2: If you haven’t installed Apache2 yet, you can do so with the following command:
    bash sudo apt install apache2
  2. Enable Necessary Apache Modules: Enable the required Apache modules for proxying HTTP requests.
    sudo a2enmod proxy
    sudo a2enmod proxy_http
    sudo a2enmod headers
    sudo a2enmod deflate
  3. Create a Virtual Host Configuration: Create or edit your Apache virtual host configuration file. For example, create a configuration file named myapp.conf in the /etc/apache2/sites-available/ directory.
    sudo vi /etc/apache2/sites-available/myapp.conf
    Add the following configuration . Let us say we use port 8035 which inturn routes the traffic to port 8000 where the gunicorn serves the web app
    <VirtualHost *:8035>
    ServerName myhobby.com
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / http://127.0.0.1:8000/
    ProxyPassReverse / http://127.0.0.1:8000/
    ErrorLog ${APACHE_LOG_DIR}/myapp_error.log CustomLog ${APACHE_LOG_DIR}/myapp_access.log combined
    </VirtualHost>
    This configuration will forward requests from myhobby.com to the Gunicorn server running on http://127.0.0.1:8000.
  4. Enable the Site Configuration: Enable your new site configuration and disable the default site configuration if necessary.
    sudo a2ensite myapp.conf
    sudo a2dissite 000-default.conf (if we are using port 80 used by default conf)
  5. Restart Apache: Restart Apache to apply the new configuration.
    sudo systemctl restart apache2
  6. Reload Apache: Restart Apache to apply the new configuration.
    sudo systemctl restart httpd
  7. Verify the app : Ensure your Flask app is running by opening a browser session with url
    http://localhost : 8035/. This should display a web page with hello world.

By setting up Apache as a reverse proxy to Gunicorn, Apache will handle incoming HTTP requests, pass them to Gunicorn, and then return the responses to the clients. This setup allows you to leverage Apache’s robust features while efficiently serving your Flask application with Gunicorn.