diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 27cf8605740..d720b835b74 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -200,6 +200,7 @@
+
diff --git a/dotnet/eng/verify-samples/AgentsSamples.cs b/dotnet/eng/verify-samples/AgentsSamples.cs
index 2f36405002b..c9701304ef7 100644
--- a/dotnet/eng/verify-samples/AgentsSamples.cs
+++ b/dotnet/eng/verify-samples/AgentsSamples.cs
@@ -427,6 +427,15 @@ internal static class AgentsSamples
],
},
+ new SampleDefinition
+ {
+ Name = "AgentWithMemory_Step06_MemoryUsingAgentMemory",
+ ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory",
+ RequiredEnvironmentVariables = ["AZURE_OPENAI_ENDPOINT"],
+ OptionalEnvironmentVariables = ["AZURE_OPENAI_API_KEY", "FOUNDRY_MODEL", "FOUNDRY_EMBEDDING_MODEL", "NEO4J_URI", "NEO4J_USER", "NEO4J_PASSWORD"],
+ SkipReason = "Requires a running Neo4j instance; standalone sample outside the repo's CPM build.",
+ },
+
// ── AgentWithRAG ────────────────────────────────────────────────────
new SampleDefinition
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj
new file mode 100644
index 00000000000..c4e28440519
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj
@@ -0,0 +1,78 @@
+
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ false
+ AgentMemoryShoppingAssistant
+
+ $(NoWarn);OPENAI001
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/ProductCatalog.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/ProductCatalog.cs
new file mode 100644
index 00000000000..2b28ec7ade6
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/ProductCatalog.cs
@@ -0,0 +1,195 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ComponentModel;
+using System.Text;
+using AgentMemory.Neo4j.Infrastructure;
+using Microsoft.Extensions.AI;
+using Neo4j.Driver;
+
+namespace AgentMemoryShoppingAssistant;
+
+///
+/// A small retail product graph plus the shopping tools that query it — the .NET counterpart of the
+/// Python retail-assistant's get_product_tools. Products live in Neo4j as :Product nodes
+/// linked to :ProductCategory / :ProductBrand nodes, so recommendations and "related
+/// products" come from graph traversals. Cypher runs through the public
+/// seam. Exposed as s so a real chat model can call them during a run — the same
+/// way Neo4jMemoryContextProvider surfaces the memory tools through AIContext.Tools when
+/// ExposeMemoryToolsFromContextProvider is enabled.
+///
+public sealed class ProductCatalog(INeo4jTransactionRunner runner)
+{
+ private readonly INeo4jTransactionRunner _runner = runner;
+
+ private static readonly (string Name, string Category, string Brand, double Price, bool InStock, int Inventory, string Description, int Popularity)[] s_seed =
+ [
+ ("Nike Air Zoom Pegasus 40", "shoes", "Nike", 130, true, 40, "Everyday running shoe with responsive cushioning.", 95),
+ ("Nike Revolution 7", "shoes", "Nike", 70, true, 60, "Lightweight, budget-friendly running shoe.", 80),
+ ("Adidas Ultraboost Light", "shoes", "Adidas", 190, true, 25, "Premium running shoe with Boost cushioning.", 90),
+ ("Asics Gel-Kayano 31", "shoes", "Asics", 165, false, 0, "Stability running shoe for overpronation.", 70),
+ ("Sony WH-1000XM5", "electronics", "Sony", 350, true, 18, "Industry-leading noise-cancelling headphones.", 92),
+ ("Bose QuietComfort Ultra", "electronics", "Bose", 330, true, 12, "Premium noise-cancelling over-ear headphones.", 85),
+ ("Apple AirPods Pro 2", "electronics", "Apple", 250, true, 50, "Wireless earbuds with active noise cancellation.", 88),
+ ("Garmin Forerunner 265", "electronics", "Garmin", 450, true, 9, "GPS running watch with training metrics.", 78),
+ ("Nike Dri-FIT Running Tee", "apparel", "Nike", 35, true, 120, "Breathable, moisture-wicking running shirt.", 65),
+ ("Adidas Own the Run Jacket","apparel", "Adidas", 80, true, 33, "Lightweight, water-repellent running jacket.", 60),
+ ];
+
+ /// Seeds the sample product graph (idempotent — safe to run every start).
+ public Task SeedAsync(CancellationToken ct = default) => this._runner.WriteAsync(async r =>
+ {
+ await r.RunAsync(
+ """
+ UNWIND $products AS row
+ MERGE (p:Product {name: row.name})
+ SET p.category = row.category, p.brand = row.brand, p.price = row.price,
+ p.in_stock = row.in_stock, p.inventory = row.inventory,
+ p.description = row.description, p.popularity = row.popularity
+ MERGE (c:ProductCategory {name: row.category})
+ MERGE (b:ProductBrand {name: row.brand})
+ MERGE (p)-[:IN_CATEGORY]->(c)
+ MERGE (p)-[:MADE_BY]->(b)
+ """,
+ new
+ {
+ products = s_seed.Select(p => (object)new Dictionary
+ {
+ ["name"] = p.Name, ["category"] = p.Category, ["brand"] = p.Brand, ["price"] = p.Price,
+ ["in_stock"] = p.InStock, ["inventory"] = p.Inventory, ["description"] = p.Description,
+ ["popularity"] = p.Popularity,
+ }).ToList(),
+ });
+ }, ct);
+
+ // ── Tools (also usable directly in the scripted demo) ────────────────────────────────────────
+
+ [Description("Search the product catalog for items matching a query, with optional category, brand, and max-price filters.")]
+ public Task SearchProductsAsync(
+ [Description("What the customer is looking for, e.g. 'running shoes'.")] string query,
+ [Description("Optional category filter: shoes, electronics, apparel.")] string? category = null,
+ [Description("Optional brand filter, e.g. 'Nike'.")] string? brand = null,
+ [Description("Optional maximum price.")] double? maxPrice = null,
+ CancellationToken ct = default) => this._runner.ReadAsync(async r =>
+ {
+ const string Cypher =
+ """
+ MATCH (p:Product)
+ WHERE ANY(w IN split(toLower($query), ' ') WHERE
+ toLower(p.name) CONTAINS w OR toLower(p.description) CONTAINS w OR toLower(p.category) CONTAINS w)
+ AND ($category IS NULL OR p.category = $category)
+ AND ($brand IS NULL OR p.brand = $brand)
+ AND ($maxPrice IS NULL OR p.price <= $maxPrice)
+ RETURN p.name AS name, p.brand AS brand, p.category AS category,
+ p.price AS price, p.in_stock AS inStock
+ ORDER BY p.popularity DESC
+ LIMIT 10
+ """;
+ var cursor = await r.RunAsync(Cypher, new { query, category, brand, maxPrice });
+ return Render("Matches", await cursor.ToListAsync());
+ }, ct);
+
+ [Description("Get personalized product recommendations, optionally biased toward a preferred brand and/or category.")]
+ public Task GetRecommendationsAsync(
+ [Description("The customer's preferred brand (from their saved preferences), if known.")] string? preferredBrand = null,
+ [Description("Optional category to recommend within.")] string? category = null,
+ [Description("How many recommendations to return.")] int limit = 5,
+ CancellationToken ct = default) => this._runner.ReadAsync(async r =>
+ {
+ const string Cypher =
+ """
+ MATCH (p:Product)
+ WHERE p.in_stock = true
+ AND ($category IS NULL OR p.category = $category)
+ WITH p, (CASE WHEN $preferredBrand IS NOT NULL AND p.brand = $preferredBrand THEN 1 ELSE 0 END) AS onBrand
+ RETURN p.name AS name, p.brand AS brand, p.category AS category, p.price AS price, p.in_stock AS inStock
+ ORDER BY onBrand DESC, p.popularity DESC
+ LIMIT $limit
+ """;
+ var cursor = await r.RunAsync(Cypher, new { preferredBrand, category, limit });
+ var header = preferredBrand is null ? "Recommended for you" : $"Recommended for you (favoring {preferredBrand})";
+ return Render(header, await cursor.ToListAsync());
+ }, ct);
+
+ [Description("Find products related to a given product — same category or same brand — via graph traversal.")]
+ public Task GetRelatedProductsAsync(
+ [Description("The exact product name to find related items for.")] string productName,
+ CancellationToken ct = default) => this._runner.ReadAsync(async r =>
+ {
+ const string Cypher =
+ """
+ MATCH (p:Product {name: $productName})
+ CALL (p) {
+ MATCH (p)-[:IN_CATEGORY]->(c)<-[:IN_CATEGORY]-(rel:Product) WHERE rel <> p
+ RETURN rel, 'same category' AS reason
+ UNION
+ MATCH (p)-[:MADE_BY]->(b)<-[:MADE_BY]-(rel:Product) WHERE rel <> p
+ RETURN rel, 'same brand' AS reason
+ }
+ WITH rel, collect(DISTINCT reason) AS reasons
+ RETURN rel.name AS name, rel.brand AS brand, rel.category AS category,
+ rel.price AS price, rel.in_stock AS inStock, rel.popularity AS popularity,
+ reduce(s = '', x IN reasons | CASE WHEN s = '' THEN x ELSE s + ', ' + x END) AS reason
+ ORDER BY popularity DESC
+ LIMIT 5
+ """;
+ var cursor = await r.RunAsync(Cypher, new { productName });
+ return Render($"Related to {productName}", await cursor.ToListAsync());
+ }, ct);
+
+ [Description("Check whether a product is in stock and how many units are available.")]
+ public Task CheckInventoryAsync(
+ [Description("The exact product name to check.")] string productName,
+ CancellationToken ct = default) => this._runner.ReadAsync(async r =>
+ {
+ var cursor = await r.RunAsync(
+ "MATCH (p:Product {name: $productName}) RETURN p.name AS name, p.in_stock AS inStock, p.inventory AS inventory",
+ new { productName });
+ var rows = await cursor.ToListAsync();
+ if (rows.Count == 0)
+ {
+ return $"'{productName}' was not found in the catalog.";
+ }
+
+ var rec = rows[0];
+ var inStock = rec["inStock"].As();
+ return inStock
+ ? $"{rec["name"].As()}: In stock ({rec["inventory"].As()} available)."
+ : $"{rec["name"].As()}: Out of stock.";
+ }, ct);
+
+ /// The retail tools as MAF/MEAI s (attach to the agent's ChatOptions.Tools).
+ public IReadOnlyList CreateAIFunctions() =>
+ [
+ AIFunctionFactory.Create(this.SearchProductsAsync, "search_products",
+ "Search the product catalog with optional category/brand/price filters."),
+ AIFunctionFactory.Create(this.GetRecommendationsAsync, "get_recommendations",
+ "Get personalized recommendations, optionally favoring a preferred brand/category."),
+ AIFunctionFactory.Create(this.GetRelatedProductsAsync, "get_related_products",
+ "Find products related to a given product via the graph."),
+ AIFunctionFactory.Create(this.CheckInventoryAsync, "check_inventory",
+ "Check stock/availability for a product."),
+ ];
+
+ private static string Render(string header, List rows)
+ {
+ if (rows.Count == 0)
+ {
+ return $"{header}: (no matches)";
+ }
+
+ var sb = new StringBuilder().Append(header).Append(':').AppendLine();
+ foreach (var rec in rows)
+ {
+ var stock = rec["inStock"].As() ? "in stock" : "out of stock";
+ var reason = rec.Keys.Contains("reason") ? $" [{rec["reason"].As()}]" : string.Empty;
+ sb.Append(" • ")
+ .Append(rec["name"].As())
+ .Append(" — ").Append(rec["brand"].As())
+ .Append(", ").Append(rec["category"].As())
+ .Append(", $").Append(rec["price"].As().ToString("0"))
+ .Append(", ").Append(stock).Append(reason)
+ .AppendLine();
+ }
+ return sb.ToString().TrimEnd();
+ }
+}
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/Program.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/Program.cs
new file mode 100644
index 00000000000..a380c1e6783
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/Program.cs
@@ -0,0 +1,156 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// Agent Memory — Shopping Assistant (Microsoft Agent Framework, .NET)
+//
+// A .NET port of the Neo4j Labs "agent-memory" retail-assistant example
+// (https://github.com/neo4j-labs/agent-memory/tree/main/examples/microsoft_agent_retail_assistant,
+// referenced from https://learn.microsoft.com/en-us/agent-framework/integrations/neo4j-memory).
+//
+// A shopping assistant that LEARNS a customer's preferences and RECOMMENDS products via graph
+// traversal, backed by DURABLE memory in Neo4j. It uses the AgentMemory library — a .NET port of the
+// Python memory provider, not an officially recognized Neo4j integration — and its Microsoft Agent
+// Framework adapter:
+// • Neo4jMemoryContextProvider (an AIContextProvider) — recalls memory before each run, persists
+// after, and (via ExposeMemoryToolsFromContextProvider) surfaces the memory tools (search/remember/
+// recall) itself through AIContext.Tools
+// • ProductCatalog.CreateAIFunctions() — retail tools over a Neo4j :Product graph
+//
+// Configuration (environment variables, matching the other Foundry samples):
+// AZURE_OPENAI_ENDPOINT (required) — your Azure OpenAI / Foundry endpoint
+// AZURE_OPENAI_API_KEY (optional) — API key; if unset, DefaultAzureCredential (az login) is used
+// FOUNDRY_MODEL (default: gpt-4o-mini) — chat model deployment
+// FOUNDRY_EMBEDDING_MODEL (default: text-embedding-3-small) — embedding model deployment (1536 dims)
+// NEO4J_URI (default: bolt://localhost:7687)
+// NEO4J_USER (default: neo4j)
+// NEO4J_PASSWORD (default: password)
+
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using AgentMemory.Abstractions.Services;
+using AgentMemory.AgentFramework;
+using AgentMemory.Core;
+using AgentMemory.Core.Stubs;
+using AgentMemory.Neo4j.Infrastructure;
+using AgentMemoryShoppingAssistant;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using OpenAI;
+
+// ── Model + credentials (Azure OpenAI / Foundry, via env vars) ───────────────────────────────────
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
+ ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
+var chatModel = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-4o-mini";
+var embeddingModel = Environment.GetEnvironmentVariable("FOUNDRY_EMBEDDING_MODEL") ?? "text-embedding-3-small";
+
+var clientOptions = new OpenAIClientOptions { Endpoint = new Uri(endpoint) };
+// API key if provided, otherwise Azure credential (dev: `az login`).
+// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
+// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
+// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
+OpenAIClient openAI = string.IsNullOrWhiteSpace(apiKey)
+ ? new OpenAIClient(new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), clientOptions)
+ : new OpenAIClient(new ApiKeyCredential(apiKey), clientOptions);
+
+IChatClient chatClient = openAI.GetChatClient(chatModel).AsIChatClient();
+IEmbeddingGenerator> embeddingGenerator =
+ openAI.GetEmbeddingClient(embeddingModel).AsIEmbeddingGenerator();
+
+// ── AgentMemory (Neo4j) DI ───────────────────────────────────────────────────────────────────────
+var builder = Host.CreateApplicationBuilder(args);
+builder.Logging.SetMinimumLevel(LogLevel.Warning);
+
+builder.Services.AddNeo4jAgentMemory(options =>
+{
+ options.Uri = Environment.GetEnvironmentVariable("NEO4J_URI") ?? "bolt://localhost:7687";
+ options.Username = Environment.GetEnvironmentVariable("NEO4J_USER") ?? "neo4j";
+ options.Password = Environment.GetEnvironmentVariable("NEO4J_PASSWORD") ?? "password";
+});
+builder.Services.AddAgentMemoryCore(_ => { });
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.TryAddSingleton(chatClient);
+builder.Services.TryAddSingleton(embeddingGenerator);
+builder.Services.AddAgentMemoryFramework(options =>
+{
+ options.AutoExtractOnPersist = true;
+ options.ContextFormat.IncludeEntities = true;
+ options.ContextFormat.IncludeFacts = true;
+ options.ContextFormat.IncludePreferences = true;
+ options.ExposeMemoryToolsFromContextProvider = true;
+});
+
+var host = builder.Build();
+await using var hostDisposal = (IAsyncDisposable)host;
+
+await using var scope = host.Services.CreateAsyncScope();
+var sp = scope.ServiceProvider;
+
+// ── Setup: schema + sample product graph ─────────────────────────────────────────────────────────
+var catalog = new ProductCatalog(sp.GetRequiredService());
+await sp.GetRequiredService().BootstrapAsync();
+await catalog.SeedAsync();
+Console.WriteLine("Neo4j schema ready; sample products loaded.\n");
+
+// ── The shopping assistant: context provider (recall + memory tools) + product tools ─────────────
+var memoryProvider = sp.GetRequiredService();
+var productTools = catalog.CreateAIFunctions();
+
+// WithMemoryOwnerScoping(sp) scopes the whole invocation (recall, tool calls, persistence) to the
+// owner set via WithMemoryIdentity below — no manual BeginOwnerScope wrapping needed per turn.
+AIAgent agent = chatClient.AsAIAgent(new ChatClientAgentOptions
+{
+ Name = "ShoppingAssistant",
+ ChatOptions = new ChatOptions
+ {
+ ModelId = chatModel,
+ Instructions =
+ "You are a helpful shopping assistant for an online store. Learn and remember the customer's "
+ + "preferences (brands, budget, categories) using the memory tools, and recommend products that "
+ + "fit using the product tools. Explain why each recommendation matches, and suggest alternatives "
+ + "when something is out of stock.",
+ // memoryProvider appends the six memory tools (search_memory, remember_fact, ...) to this list
+ // on every model call via AIContext.Tools — see ExposeMemoryToolsFromContextProvider above.
+ Tools = [.. productTools],
+ },
+ AIContextProviders = [memoryProvider],
+}).WithMemoryOwnerScoping(sp);
+
+const string Shopper = "shopper-amelia";
+
+// ── Session A — the customer shops; the model calls the tools and remembers preferences ──────────
+Console.WriteLine(">> Session A\n");
+var sessionA = (await agent.CreateSessionAsync())
+ .WithMemoryIdentity(userId: Shopper, sessionId: "cart-a", applicationId: "retail-demo");
+
+foreach (var turn in new[]
+{
+ "Hi! I'm looking for running shoes. I love Nike and want to stay under $150.",
+ "Nice — what would you recommend for me, and is anything I might like out of stock?",
+})
+{
+ await SayAsync(agent, sessionA, turn);
+}
+
+// ── Session B — a NEW session for the same shopper still recalls her preferences ─────────────────
+Console.WriteLine(">> Session B — a brand-new session; memory is durable\n");
+var sessionB = (await agent.CreateSessionAsync())
+ .WithMemoryIdentity(userId: Shopper, sessionId: "cart-b", applicationId: "retail-demo");
+
+await SayAsync(agent, sessionB, "I'm back — remind me what I like and suggest something new.");
+
+Console.WriteLine("=== Done. Preferences + messages persist in Neo4j across sessions. ===");
+
+// One conversational turn. Owner scoping (recall, tool calls, and persistence) is guaranteed
+// automatically by the WithMemoryOwnerScoping-wrapped agent — no manual BeginOwnerScope needed here.
+static async Task SayAsync(AIAgent agent, AgentSession session, string message)
+{
+ Console.WriteLine($"USER : {message}");
+ var response = await agent.RunAsync(message, session);
+ Console.WriteLine($"ASSISTANT : {response.Text}\n");
+}
diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/README.md b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/README.md
new file mode 100644
index 00000000000..46800724722
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/README.md
@@ -0,0 +1,75 @@
+# Agent with Memory Using AgentMemory — Shopping Assistant
+
+A **.NET port of the Neo4j Labs "agent-memory" retail assistant** example
+([`microsoft_agent_retail_assistant`](https://github.com/neo4j-labs/agent-memory/tree/main/examples/microsoft_agent_retail_assistant),
+referenced from the [Learn integration page](https://learn.microsoft.com/en-us/agent-framework/integrations/neo4j-memory)).
+A shopping assistant that **learns a customer's preferences** and **recommends products via graph
+traversal**, backed by durable memory in Neo4j.
+
+It uses the [`AgentMemory`](https://www.nuget.org/packages/AgentMemory) library — a .NET port of the
+(Python-only) Neo4j Labs memory provider, **not an officially recognized Neo4j integration** — through
+its Microsoft Agent Framework adapter.
+
+## Features Demonstrated
+
+- **`Neo4jMemoryContextProvider`** (an `AIContextProvider`) — recalls relevant memory before each run,
+ persists new memory after (the same bidirectional pattern as the official provider), and — via
+ `ExposeMemoryToolsFromContextProvider = true` — surfaces the memory tools (search / remember / recall)
+ itself through `AIContext.Tools`.
+- **`ProductCatalog.CreateAIFunctions()`** — retail tools over a Neo4j `:Product` graph (search /
+ recommend / related / inventory).
+- Preference learning that persists across a brand-new `AgentSession` for the same shopper.
+- Graph-based product recommendations and "related products" via traversal.
+
+## Prerequisites
+
+- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+- A **Neo4j 5.x** instance (the sample bootstraps the schema and seeds sample products)
+- An **Azure OpenAI / Foundry** deployment (a chat model + an embedding model)
+
+
+## Configuration
+
+Set the following environment variables:
+
+| Variable | Required | Default | Purpose |
+|---|---|---|---|
+| `AZURE_OPENAI_ENDPOINT` | ✅ | — | Azure OpenAI / Foundry endpoint |
+| `AZURE_OPENAI_API_KEY` | — | — | API key; if unset, `DefaultAzureCredential` (`az login`) is used |
+| `FOUNDRY_MODEL` | — | `gpt-4o-mini` | chat model deployment |
+| `FOUNDRY_EMBEDDING_MODEL` | — | `text-embedding-3-small` | embedding model deployment (1536 dims) |
+| `NEO4J_URI` | — | `bolt://localhost:7687` | Neo4j bolt URI |
+| `NEO4J_USER` | — | `neo4j` | Neo4j user |
+| `NEO4J_PASSWORD` | — | `password` | Neo4j password |
+
+> Ensure the embedding model's dimensions match the Neo4j vector-index dimensions AgentMemory bootstraps
+> (default 1536, which matches `text-embedding-3-small`).
+
+## Run the Sample
+
+```bash
+docker run -d --name neo4j -p 7474:7474 -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j:5.26
+
+export AZURE_OPENAI_ENDPOINT="https://.openai.azure.com"
+export AZURE_OPENAI_API_KEY="" # or omit and `az login`
+export FOUNDRY_MODEL="gpt-4o-mini"
+
+dotnet run
+```
+
+## Expected Output
+
+1. The sample bootstraps the Neo4j schema and seeds a small product graph (`:Product`,
+ `:ProductCategory`, `:ProductBrand` nodes).
+2. **Session A** — the shopper says she wants running shoes, loves Nike, and has a $150 budget; the
+ agent calls the memory tools to remember this and the product tools to recommend matching items.
+3. **Session B** — a brand-new session for the same shopper (`shopper-amelia`) still recalls her
+ preferences and can suggest something new, because memory persists in Neo4j across sessions.
+
+## Note on packaging
+
+This sample is part of the repo's solution and targets .NET 10 like every other sample, but it
+deliberately opts out of **Central Package Management** and does **not** reference `Microsoft.Agents.AI`
+via the repo's in-source project — it consumes the **published** `AgentMemory` NuGet packages instead
+(which target `Microsoft.Agents.AI` 1.9.0). A version that references the repo's current
+`Microsoft.Agents.AI` source would require AgentMemory to be rebuilt against that version first.
diff --git a/dotnet/samples/02-agents/AgentWithMemory/README.md b/dotnet/samples/02-agents/AgentWithMemory/README.md
index 3b9ee76928f..752635bbb24 100644
--- a/dotnet/samples/02-agents/AgentWithMemory/README.md
+++ b/dotnet/samples/02-agents/AgentWithMemory/README.md
@@ -9,6 +9,7 @@ These samples show how to create an agent with the Agent Framework that uses Mem
|[Custom Memory Implementation](../../01-get-started/04_memory/)|This sample demonstrates how to create a custom memory component and attach it to an agent.|
|[Memory with Microsoft Foundry](./AgentWithMemory_Step04_MemoryUsingFoundry/)|This sample demonstrates how to create and run an agent that uses Microsoft Foundry's managed memory service to extract and retrieve individual memories.|
|[Bounded Chat History with Overflow](./AgentWithMemory_Step05_BoundedChatHistory/)|This sample demonstrates how to create a bounded chat history provider that overflows older messages to a vector store and recalls them as memories.|
+|[Memory Using AgentMemory](./AgentWithMemory_Step06_MemoryUsingAgentMemory/)|This sample demonstrates a retail shopping assistant built with [`AgentMemory`](https://www.nuget.org/packages/AgentMemory), an unofficial .NET port of the Neo4j Labs graph-memory provider, to learn customer preferences and recommend products via graph traversal.|
> **See also**: [Memory Search with Foundry Agents](../AgentProviders/foundry/Agent_Step22_MemorySearch/) - demonstrates using the built-in Memory Search tool with Microsoft Foundry agents.
diff --git a/dotnet/samples/AGENTS.md b/dotnet/samples/AGENTS.md
index 348e4c53385..e5432596eb1 100644
--- a/dotnet/samples/AGENTS.md
+++ b/dotnet/samples/AGENTS.md
@@ -30,7 +30,7 @@ dotnet/samples/
│ │ └── openai/ # OpenAI provider samples
│ ├── AgentOpenTelemetry/ # OpenTelemetry integration
│ ├── AgentSkills/ # Agent skills patterns
-│ ├── AgentWithMemory/ # Memory providers (chat history, Mem0, Foundry)
+│ ├── AgentWithMemory/ # Memory providers (chat history, Mem0, Valkey, Foundry, AgentMemory)
│ ├── AgentWithRAG/ # RAG patterns (text, vector store, Foundry)
│ ├── AGUI/ # AG-UI protocol samples
│ ├── DeclarativeAgents/ # Declarative agent definitions