memory
How to add memory to an agent using an AIContextProvider.
$ npx -y skills add managedcode/dotnet-skills --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
How to add memory to an agent using an AIContextProvider.
Agent definition
memory.mdtitle: Adding Memory to an Agent
description: How to add memory to an agent using an AIContextProvider.
zone_pivot_groups: programming-languages
author: westey-m
ms.topic: tutorial
ms.author: westey
ms.date: 09/25/2025
ms.service: agent-framework
Adding Memory to an Agent
::: zone pivot="programming-language-csharp" This tutorial shows how to add memory to an agent by implementing an `AIContextProvider` and attaching it to the agent.
> [!IMPORTANT] > Not all agent types support `AIContextProvider`. This step uses a `ChatClientAgent`, which does support `AIContextProvider`.
Prerequisites
For prerequisites and installing NuGet packages, see the [Create and run a simple agent](./run-agent.md) step in this tutorial.
Create an AIContextProvider
`AIContextProvider` is an abstract class that you can inherit from, and which can be associated with the `AgentThread` for a `ChatClientAgent`. It allows you to:
1. Run custom logic before and after the agent invokes the underlying inference service. 1. Provide additional context to the agent before it invokes the underlying inference service. 1. Inspect all messages provided to and produced by the agent.
Pre and post invocation events
The `AIContextProvider` class has two methods that you can override to run custom logic before and after the agent invokes the underlying inference service:
- `InvokingAsync` - called before the agent invokes the underlying inference service. You can provide additional context to the agent by returning an `AIContext` object. This context will be merged with the agent's existing context before invoking the underlying service. It is possible to provide instructions, tools, and messages to add to the request.
- `InvokedAsync` - called after the agent has received a response from the underlying inference service. You can inspect the request and response messages, and update the state of the context provider.
Serialization
`AIContextProvider` instances are created and attached to an `AgentThread` when the thread is created, and when a thread is resumed from a serialized state.
The `AIContextProvider` instance might have its own state that needs to be persisted between invocations of the agent. For example, a memory component that remembers information about the user might have memories as part of its state.
To allow persisting threads, you need to implement the `SerializeAsync` method of the `AIContextProvider` class. You also need to provide a constructor that takes a `JsonElement` parameter, which can be used to deserialize the state when resuming a thread.
Sample AIContextProvider implementation
The following example of a custom memory component remembers a user's name and age and provides it to the agent before each invocation.
First, create a model class to hold the memories.
internal sealed class UserInfo
{
public string? UserName { get; set; }
public int? UserAge { get; set; }
}Then you can implement the `AIContextProvider` to manage the memories. The `UserInfoMemory` class below contains the following behavior:
1. It uses an `IChatClient` to look for the user's name and age in user messages when new messages are added to the thread at the end of each run. 1. It provides any current memories to the agent before each invocation. 1. If no memories are available, it instructs the agent to ask the user for the missing information, and not to answer any questions until the information is provided. 1. It also implements serialization to allow persisting the memories as part of the thread state.
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
internal sealed class UserInfoMemory : AIContextProvider
{
private readonly IChatClient _chatClient;
public UserInfoMemory(IChatClient chatClient, UserInfo? userInfo = null)
{
this._chatClient = chatClient;
this.UserInfo = userInfo ?? new UserInfo();
}
public UserInfoMemory(IChatClient chatClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
{
this._chatClient = chatClient;
this.UserInfo = serializedState.ValueKind == JsonValueKind.Object ?
serializedState.Deserialize<UserInfo>(jsonSerializerOptions)! :
new UserInfo();
}
public UserInfo UserInfo { get; set; }
public override async ValueTask InvokedAsync(
InvokedContext context,
CancellationToken cancellationToken = default)
{
if ((this.UserInfo.UserName is null || this.UserInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
{
var result = await this._chatClient.GetResponseAsync<UserInfo>(
context.RequestMessages,
new ChatOptions()
{
Instructions = "Extract the user's name and age from the message if present. If not present return nulls."
},
cancellationToken: cancellationToken);
this.UserInfo.UserName ??= result.Result.UserName;
this.UserInfo.UserAge ??= result.Result.UserAge;
}
}
public override ValueTask<AIContext> InvokingAsync(
InvokingContext context,
CancellationToken cancellationToken = default)
{
StringBuilder instructions = new();
instructions
.AppendLine(
this.UserInfo.UserName is null ?
"Ask the user for their name and politely decline to answer any questions until they provide it." :
$"The user's name is {this.UserInfo.UserName}.")
.AppendLine(
this.UserInfo.UserAge is null ?
"Ask the user for their age and politely decline to answer any questions until they provide it." :
$"The user's age is {this.UserInRead more
title: Adding Memory to an Agent description: How to add memory to an agent using an AIContextProvider. zone_pivot_groups: programming-languages author: westey-m ms.topic: tutorial ms.author: westey ms.date: 09/25/2025 ms.service: agent-framework
Adding Memory to an Agent
::: zone pivot="programming-language-csharp" This tutorial shows how to add memory to an agent by implementing an `AIContextProvider` and attaching it to the agent.
> [!IMPORTANT] > Not all agent types support `AIContextProvider`. This step uses a `ChatClientAgent`, which does support `AIContextProvider`.
Prerequisites
For prerequisites and installing NuGet packages, see the [Create and run a simple agent](./run-agent.md) step in this tutorial.
Create an AIContextProvider
`AIContextProvider` is an abstract class that you can inherit from, and which can be associated with the `AgentThread` for a `ChatClientAgent`. It allows you to:
1. Run custom logic before and after the agent invokes the underlying inference service. 1. Provide additional context to the agent before it invokes the underlying inference service. 1. Inspect all messages provided to and produced by the agent.
Pre and post invocation events
The `AIContextProvider` class has two methods that you can override to run custom logic before and after the agent invokes the underlying inference service:
- `InvokingAsync` - called before the agent invokes the underlying inference service. You can provide additional context to the agent by returning an `AIContext` object. This context will be merged with the agent's existing context before invoking the underlying service. It is possible to provide instructions, tools, and messages to add to the request.
- `InvokedAsync` - called after the agent has received a response from the underlying inference service. You can inspect the request and response messages, and update the state of the context provider.
Serialization
`AIContextProvider` instances are created and attached to an `AgentThread` when the thread is created, and when a thread is resumed from a serialized state.
The `AIContextProvider` instance might have its own state that needs to be persisted between invocations of the agent. For example, a memory component that remembers information about the user might have memories as part of its state.
To allow persisting threads, you need to implement the `SerializeAsync` method of the `AIContextProvider` class. You also need to provide a constructor that takes a `JsonElement` parameter, which can be used to deserialize the state when resuming a thread.
Sample AIContextProvider implementation
The following example of a custom memory component remembers a user's name and age and provides it to the agent before each invocation.
First, create a model class to hold the memories.
internal sealed class UserInfo
{
public string? UserName { get; set; }
public int? UserAge { get; set; }
}Then you can implement the `AIContextProvider` to manage the memories. The `UserInfoMemory` class below contains the following behavior:
1. It uses an `IChatClient` to look for the user's name and age in user messages when new messages are added to the thread at the end of each run. 1. It provides any current memories to the agent before each invocation. 1. If no memories are available, it instructs the agent to ask the user for the missing information, and not to answer any questions until the information is provided. 1. It also implements serialization to allow persisting the memories as part of the thread state.
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
internal sealed class UserInfoMemory : AIContextProvider
{
private readonly IChatClient _chatClient;
public UserInfoMemory(IChatClient chatClient, UserInfo? userInfo = null)
{
this._chatClient = chatClient;
this.UserInfo = userInfo ?? new UserInfo();
}
public UserInfoMemory(IChatClient chatClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
{
this._chatClient = chatClient;
this.UserInfo = serializedState.ValueKind == JsonValueKind.Object ?
serializedState.Deserialize<UserInfo>(jsonSerializerOptions)! :
new UserInfo();
}
public UserInfo UserInfo { get; set; }
public override async ValueTask InvokedAsync(
InvokedContext context,
CancellationToken cancellationToken = default)
{
if ((this.UserInfo.UserName is null || this.UserInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
{
var result = await this._chatClient.GetResponseAsync<UserInfo>(
context.RequestMessages,
new ChatOptions()
{
Instructions = "Extract the user's name and age from the message if present. If not present return nulls."
},
cancellationToken: cancellationToken);
this.UserInfo.UserName ??= result.Result.UserName;
this.UserInfo.UserAge ??= result.Result.UserAge;
}
}
public override ValueTask<AIContext> InvokingAsync(
InvokingContext context,
CancellationToken cancellationToken = default)
{
StringBuilder instructions = new();
instructions
.AppendLine(
this.UserInfo.UserName is null ?
"Ask the user for their name and politely decline to answer any questions until they provide it." :
$"The user's name is {this.UserInfo.UserName}.")
.AppendLine(
this.UserInfo.UserAge is null ?
"Ask the user for their age and politely decline to answer any questions until they provide it." :
$"The user's age is {this.UserInStop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.
Repo: managedcode/dotnet-skills
Other agents on dotnet-skills.
- AGENT
Specialist orchestration agent for .NET Aspire work. Use when the problem is clearly about AppHost design, ServiceDefaults, first-party versus CommunityToolkit/Aspire integrations, dashboard and testing, `DistributedApplicationTestingBuilder`, `WebApplicationFactory`
Open agent - agent-as-function-tool
Legacy tutorial alias retained locally; the live Learn URL now resolves into the broader Function Tools surface
Open agent - agent-as-mcp-tool
Learn how to expose an agent as a tool over the MCP protocol
Open agent - create-and-run-durable-agent
Learn how to create and run a durable AI agent with Azure Functions and the durable task extension for Microsoft Agent Framework
Open agent - enable-observability
Enable OpenTelemetry for an agent so agent interactions are automatically logged
Open agent - function-tools-approvals
Learn how to use function tools with human in the loop approvals
Open agent

