Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step04_MemoryUsingFoundry/AgentWithMemory_Step04_MemoryUsingFoundry.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory/AgentWithMemory_Step06_MemoryUsingAgentMemory.csproj" />
<Project Path="samples/02-agents/AgentWithMemory/AgentWithMemory_Step07_FileMemoryProvider/AgentWithMemory_Step07_FileMemoryProvider.csproj" />
</Folder>
<Folder Name="/Samples/02-agents/AgentProviders/openai/">
<File Path="samples/02-agents/AgentProviders/openai/README.md" />
Expand Down
22 changes: 22 additions & 0 deletions dotnet/eng/verify-samples/AgentsSamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,28 @@ internal static class AgentsSamples
SkipReason = "Requires a running Neo4j instance; standalone sample outside the repo's CPM build.",
},

new SampleDefinition
{
Name = "AgentWithMemory_Step07_FileMemoryProvider",
ProjectPath = "samples/02-agents/AgentWithMemory/AgentWithMemory_Step07_FileMemoryProvider",
RequiredEnvironmentVariables = ["FOUNDRY_PROJECT_ENDPOINT"],
OptionalEnvironmentVariables = ["FOUNDRY_MODEL"],
MustContain =
[
"Memory files will be written to:",
"=== First conversation ===",
"=== Memory files on disk ===",
"=== Second conversation (new session) ===",
],
ExpectedOutputDescription =
[
"The output should acknowledge that the user is vegetarian and travels with a dog, indicating the agent stored these preferences.",
"The memory files section should list at least one memory file written by the agent, such as a file about the user's preferences.",
"The second conversation should recommend a hotel and a restaurant in Paris that are consistent with the remembered preferences, for example a pet-friendly hotel and a restaurant with vegetarian options, even though it is a new session.",
"The output should not contain error messages or stack traces.",
],
},

// ── AgentWithRAG ────────────────────────────────────────────────────

new SampleDefinition
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>

<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.

// This sample shows how to give an agent file-based memory using the FileMemoryProvider.
// The FileMemoryProvider exposes a set of tools to the agent (write, read, delete, list, grep and replace)
// that allow it to store memories as individual files in an AgentFileStore.
// Because the files are stored outside of the conversation, the agent can recall them
// in later conversations, even after the original chat history is gone.
//
// The sample also shows how to control the folder that memory files are written to,
// by supplying a state initializer callback that sets the working folder for each session.

#pragma warning disable MAAI001 // AgentFileStore and its implementations are experimental.

using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;

var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";

// The id of the user that we are storing memories for.
// It is used below to give each user their own memory folder.
const string UserId = "UID1";

// Create the file store that the FileMemoryProvider will use to persist memory files.
// Here we use a file system backed store rooted at a local folder called "agent-memory",
// but any AgentFileStore implementation can be used, e.g. InMemoryAgentFileStore or a custom
// implementation backed by blob storage.
var memoryRoot = Path.Combine(AppContext.BaseDirectory, "agent-memory");
var fileStore = new FileSystemAgentFileStore(memoryRoot);

// The working folder that memories for this user will be written to, relative to the store root.
// The folder you choose determines the scope and lifetime of the memories:
// - A stable folder, like the per-user one below, gives you durable memories that are shared by
// every session for that user. That is what allows the second conversation further down to
// recall what the user said in the first.
// - A unique folder per session gives you memories that are isolated to a single session, e.g.
// generate one in the state initializer callback below:
// _ => new FileMemoryState { WorkingFolder = Guid.NewGuid().ToString() }
var workingFolder = $"users/{UserId}";

Console.WriteLine($"Memory files will be written to: {Path.Combine(memoryRoot, workingFolder)}");
Console.WriteLine();

// Create the file memory provider.
// The second parameter is a state initializer callback that is invoked whenever the provider
// cannot find existing state in a session, i.e. typically the first time it is used with a new session.
// It allows us to configure the folder that memory files for that session are written to.
// If no callback is supplied, the working folder defaults to the root of the store,
// which means all sessions share a single, flat set of memory files.
using var fileMemoryProvider = new FileMemoryProvider(
fileStore,
_ => new FileMemoryState { WorkingFolder = workingFolder });

// Create the agent and attach the FileMemoryProvider so that the agent gets the file memory tools.
AIAgent agent = new AIProjectClient(
new Uri(endpoint),
// 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.
new DefaultAzureCredential())
.AsAIAgent(new ChatClientAgentOptions
{
ChatOptions = new()
{
ModelId = deploymentName,
Instructions = "You are a helpful travel assistant. Remember what the user tells you about themselves so that you can give better recommendations later."
},
Name = "TravelAssistant",
AIContextProviders = [fileMemoryProvider],
});

// First conversation: tell the agent something worth remembering.
// The agent should use the file_memory_write tool to store it as a file in the working folder.
AgentSession firstSession = await agent.CreateSessionAsync();
Console.WriteLine("=== First conversation ===");
Console.WriteLine(await agent.RunAsync(
"I'm vegetarian and I always travel with my dog. Please remember this for future trips.",
firstSession));
Console.WriteLine();

// Show the memory files that the agent created on disk.
Console.WriteLine("=== Memory files on disk ===");
foreach (var file in Directory.EnumerateFiles(Path.Combine(memoryRoot, workingFolder)))
{
Console.WriteLine(Path.GetFileName(file));
}

Console.WriteLine();

// Second conversation: a brand new session with no chat history from the first conversation.
// The provider surfaces the memory index to the agent, and the agent can read the memory files
// using the file_memory_read tool, so it can still recall the user's preferences.
AgentSession secondSession = await agent.CreateSessionAsync();
Console.WriteLine("=== Second conversation (new session) ===");
Console.WriteLine(await agent.RunAsync(
"Suggest a hotel and a restaurant for my trip to Paris next week.",
secondSession));
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# File Based Memory with FileMemoryProvider

This sample demonstrates how to give an agent file-based memory using the `FileMemoryProvider`.

The `FileMemoryProvider` is an `AIContextProvider` that exposes a set of memory tools to the agent, allowing the agent to decide what to remember and when to recall it. Each memory is stored as an individual file in an `AgentFileStore`, so memories survive beyond the lifetime of a single conversation.

## Concepts

- **`FileMemoryProvider`**: An `AIContextProvider` that adds the following tools to the agent:

| Tool | Description |
|---|---|
| `file_memory_write` | Write a memory file with a name, content and optional description. |
| `file_memory_read` | Read the content of a memory file by name. |
| `file_memory_delete` | Delete a memory file by name. |
| `file_memory_ls` | List all memory files with their descriptions. |
| `file_memory_grep` | Search memory file contents using a regular expression. |
| `file_memory_replace` | Replace occurrences of a substring within a memory file. |
| `file_memory_replace_lines` | Replace whole lines within a memory file. |

The provider also maintains a `memories.md` index file, which it injects into the conversation so the agent knows which memories are available without having to list them first.

- **`AgentFileStore`**: The pluggable storage abstraction used by the provider. This sample uses `FileSystemAgentFileStore` to store memories on the local disk, but `InMemoryAgentFileStore` or a custom implementation (e.g. backed by blob storage) can be used instead.

- **`FileMemoryState`**: The per-session state of the provider. Its `WorkingFolder` property determines the folder, relative to the store root, that memory files are written to.

## Configuring the memory folder

By default, all sessions share the root folder of the store, which means every session reads and writes the same flat set of memory files.

To scope memories, e.g. per user, per tenant or per session, pass a state initializer callback to the `FileMemoryProvider` constructor. The callback receives the `AgentSession` and is invoked whenever the provider cannot find existing state in that session, i.e. typically the first time the provider is used with a new session:

```csharp
using var fileMemoryProvider = new FileMemoryProvider(
fileStore,
session => new FileMemoryState { WorkingFolder = $"users/{userId}" });
```

In this sample, memories are written to `agent-memory/users/UID1` under the application's base directory. Because the folder is derived from a fixed user id rather than the session, a new session for the same user picks up the memories written by earlier sessions.

## Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- A Microsoft Foundry project with a chat model deployment
- Run `az login` to authenticate with `DefaultAzureCredential`

## Configuration

Set the following environment variables:

| Variable | Description | Default |
|---|---|---|
| `FOUNDRY_PROJECT_ENDPOINT` | Your Foundry project endpoint | *(required)* |
| `FOUNDRY_MODEL` | Chat model deployment name | `gpt-5.4-mini` |

## Running the Sample

```bash
dotnet run
```

## How it Works

1. A `FileSystemAgentFileStore` is created, rooted at a local `agent-memory` folder.
2. A `FileMemoryProvider` is created over that store, with a state initializer that puts the memories for the current user in their own working folder.
3. The provider is attached to the agent via `ChatClientAgentOptions.AIContextProviders`, which gives the agent the `file_memory_*` tools and instructions for using them.
4. In the first conversation, the user shares some preferences and the agent calls `file_memory_write` to store them as a file in the working folder. The sample then lists the files that were created on disk.
5. In the second conversation, a brand new session is created with no chat history from the first conversation. The provider injects the memory index into the conversation, and the agent calls `file_memory_read` to recall the stored preferences when making its recommendations.
1 change: 1 addition & 0 deletions dotnet/samples/02-agents/AgentWithMemory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ These samples show how to create an agent with the Agent Framework that uses Mem
|[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.|
|[File Based Memory](./AgentWithMemory_Step07_FileMemoryProvider/)|This sample demonstrates how to use the `FileMemoryProvider` to give an agent tools for storing and recalling memories as files, and how to configure the folder that those memory files are written to.|

> **See also**: [Memory Search with Foundry Agents](../AgentProviders/foundry/Agent_Step22_MemorySearch/) - demonstrates using the built-in Memory Search tool with Microsoft Foundry agents.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ namespace Microsoft.Agents.AI;
/// <para>
/// The <see cref="FileAccessProvider"/> gives agents the ability to work with files
/// in a folder that the user has granted access to. Unlike <see cref="FileMemoryProvider"/>,
/// which provides session-scoped memory that may be isolated per session, <see cref="FileAccessProvider"/>
/// which provides agent-managed memory files whose scope is determined by the working folder it is
/// configured with, <see cref="FileAccessProvider"/>
/// operates on a shared, persistent folder whose contents are visible across sessions and agents.
/// This makes it suitable for reading input data, writing output artifacts, and working with
/// files that have a lifetime beyond any single agent session.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ public sealed class FileMemoryProvider : AIContextProvider, IDisposable
private const string DefaultInstructions =
"""
## File Based Memory
You have access to a session-scoped, file-based memory system via the `file_memory_*` tools for storing and retrieving information across interactions.
These files act as your working memory for the current session and are isolated from other sessions.
You have access to a file-based memory system via the `file_memory_*` tools for storing and retrieving information across interactions.
These files act as your working memory and persist beyond the current conversation, so they may already contain memories written earlier,
and anything you write now may remain available later.
Use these tools to store plans, memories, processing results, or downloaded data.

- Use descriptive file names (e.g., "projectarchitecture.md", "userpreferences.md").
Expand Down Expand Up @@ -507,7 +508,7 @@ private static bool IsInternalFile(string fileName) =>

/// <summary>
/// Returns <see langword="true"/> if the normalized file name points into a subdirectory.
/// File memory is a flat, session-scoped space, so nested names are rejected up front.
/// File memory is a flat namespace within the working folder, so nested names are rejected up front.
/// </summary>
private static bool IsNestedPath(string normalizedFileName) =>
normalizedFileName.IndexOf('/') >= 0;
Expand Down
Loading