Integrations
Semantic Kernel agent memory
Give a Semantic Kernel agent durable, entity-scoped memory that persists across sessions and processes — backed by Aether, wired in with a few lines.
A Semantic Kernel agent forgets everything when a thread ends: the chat history is a whiteboard wiped between conversations. The AetherMemoryProvider fixes that. It's a Semantic Kernel AIContextProvider you attach to an agent thread; from then on the agent remembers what a user tells it and recalls the most relevant memories on every future turn — including from a brand-new chat, in a new process, days later.
Memory lives in Aether, not in the thread. Two independent sessions that share the same entity id share the same memory, and nothing is embedded client-side — Aether does the storage and retrieval.
Looking for retrieval instead of memory?
This guide covers agent memory — remembering what a user says and recalling it later. If you instead want to ground answers in a document store (classic RAG over your own files), see Semantic Kernel retrieval, which wires Aether in as a kernel plugin.
How it works
AetherMemoryProvider derives from Semantic Kernel's AIContextProvider and hooks two points in the agent's turn:
- When a message is added to the thread, the provider remembers it — stored in Aether, scoped to the configured entity. User messages are remembered by default; system and tool messages are always ignored.
- Before the model runs, the provider recalls the memories most relevant to the incoming user message and injects them as additional instructions, so the model answers with the right context in hand.
Everything is scoped to a single entity id — a stable identifier for the person or agent whose memories these are (a user id, customer id, or agent id). One provider serves one memory owner.
Prerequisites
- .NET 8.0 or newer.
- An Aether account and API key — create one free at platform.aetherdb.ai and mint a key on the API Keys page.
- A chat model for the agent. This guide uses OpenAI; Semantic Kernel supports Azure OpenAI, Anthropic, Google, and others via connector packages — swap the chat-completion service to change providers.
Install
Add the provider and the Semantic Kernel agent packages to your project:
dotnet add package AetherDb.Sdk.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
The package installs as AetherDb.Sdk.SemanticKernel; the namespace stays Aether.Sdk.SemanticKernel (using Aether.Sdk.SemanticKernel;).
Experimental agent surface
Semantic Kernel's agent framework and its AIContextProvider memory hook are still marked experimental, so the compiler emits diagnostic SKEXP0110. Opt in by adding it to NoWarn in your .csproj (shown below). Nothing else is needed — the provider suppresses its own experimental diagnostics internally.
Wire it in
Attaching memory to an agent thread is three lines: build the client, create the thread, add the provider.
using Aether.Sdk;
using Aether.Sdk.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using var aether = new AetherClient(new AetherClientOptions { ApiKey = aetherApiKey });
AgentThread thread = new ChatHistoryAgentThread();
thread.AIContextProviders.Add(new AetherMemoryProvider(aether, entityId: "traveler-dana"));
From here, invoke your agent with thread as usual — the provider does the remembering and recalling automatically. You can share one AetherClient across many entities; the provider never disposes a client you hand it (ownership stays with you).
Full working example
This console app proves memory survives a fresh session. It runs two independent sessions — each builds its own agent, its own thread (empty chat history), and its own provider. Nothing carries over in-process; the only thing the two sessions share is Aether. Session 1 teaches the agent a couple of preferences; Session 2 starts a brand-new chat and the agent still knows them.
Project file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Opt in to Semantic Kernel's experimental agent framework. -->
<NoWarn>$(NoWarn);SKEXP0110</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AetherDb.Sdk.SemanticKernel" Version="0.1.0" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.Core" Version="1.78.0" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.78.0" />
</ItemGroup>
</Project>
Program.cs
using Aether.Sdk;
using Aether.Sdk.SemanticKernel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
// --- Configuration (read from the environment) ---
string openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException("Set OPENAI_API_KEY.");
string? aetherKey = Environment.GetEnvironmentVariable("AETHER_API_KEY");
// The memory owner: a stable id for the person (or agent) whose memories these are.
const string entityId = "traveler-dana";
// One Aether client for the whole app. The provider never disposes a client it is
// given — ownership stays here.
using var aether = new AetherClient(new AetherClientOptions { ApiKey = aetherKey });
// Runs one *independent* session: a brand-new agent, a brand-new thread (empty chat
// history), and a brand-new memory provider. Nothing carries over in-process between
// calls — the only thing two sessions share is Aether.
async Task<string> RunSessionAsync(string userMessage)
{
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o-mini", openAiKey);
Kernel kernel = builder.Build();
var agent = new ChatCompletionAgent
{
Name = "Concierge",
Instructions = "You are a travel concierge. Use anything you remember about the traveler to personalize your reply.",
Kernel = kernel,
};
// A fresh thread => empty history. Attach Aether-backed memory: each user turn is
// remembered, and the most relevant past memories are recalled and injected before
// the model runs.
AgentThread thread = new ChatHistoryAgentThread();
thread.AIContextProviders.Add(new AetherMemoryProvider(aether, entityId));
string reply = string.Empty;
await foreach (AgentResponseItem<ChatMessageContent> response in agent.InvokeAsync(userMessage, thread))
{
reply = response.Message.Content ?? string.Empty;
}
return reply;
}
// --- Session 1: teach the agent something, then the "process" ends ---
Console.WriteLine("Session 1");
const string intro = "Hi! I'm Dana. I always book a window seat and I'm vegetarian.";
Console.WriteLine($"You: {intro}");
Console.WriteLine($"Agent: {await RunSessionAsync(intro)}\n");
// --- Session 2: a brand-new chat. Only Aether carries the past forward ---
Console.WriteLine("Session 2 (new chat, fresh history)");
const string ask = "Book me a flight to Lisbon — you know my preferences.";
Console.WriteLine($"You: {ask}");
Console.WriteLine($"Agent: {await RunSessionAsync(ask)}");
Run it
export OPENAI_API_KEY="sk-..."
export AETHER_API_KEY="aether_your_key_here"
dotnet run
Session 2 opens a fresh thread with no chat history, so the only way the agent can know Dana's preferences is by recalling them from Aether:
Session 1
You: Hi! I'm Dana. I always book a window seat and I'm vegetarian.
Agent: Nice to meet you, Dana! I've noted that you prefer a window seat and vegetarian meals.
Session 2 (new chat, fresh history)
You: Book me a flight to Lisbon — you know my preferences.
Agent: On it, Dana — I'll find a Lisbon flight with a window seat and request a
vegetarian meal for you.
Run the program a second time and Session 1's recall will surface what earlier runs stored, too — the memory outlives the process.
Configuration
Scoping memory to an entity
The entity id is the memory boundary. Give each user (or each agent) their own id and their memories never mix:
thread.AIContextProviders.Add(new AetherMemoryProvider(aether, entityId: user.Id));
Entity ids are 1–256 characters. If you already hold an entity-scoped Memory from the core SDK, hand it to the provider directly with new AetherMemoryProvider(memory).
Connection
AetherClientOptions resolves configuration from explicit values first, then the environment:
ApiKey— explicit value, else theAETHER_API_KEYenvironment variable.BaseUrl— explicit value, elseAETHER_BASE_URL, elsehttps://api.aetherdb.ai.
Tuning AetherMemoryProviderOptions
Pass an options object as the last constructor argument to tune recall and what gets remembered. Every value has a sensible default, so new AetherMemoryProviderOptions() is valid.
| Option | Type | Default | What it does |
|---|---|---|---|
K | int | 5 | Maximum remembered items injected into the model context before a turn. |
RecencyWeight | double | 0 | Blend in [0, 1] trading pure relevance (0) for recency. Values above 0 favor more recent memories, at some extra lookup cost. |
MinRelevance | double? | null | Optional minimum relevance in [0, 1] (higher = stricter). Recalled items scoring below this are dropped before injection. null keeps all K results. |
ContextPrompt | string | (short preamble) | Text placed before the injected memories in the model context. |
ExtractFacts | bool | false | When true, remembered messages are distilled into atomic facts server-side (requires fact extraction to be configured for your account). |
IncludeAssistantMessages | bool | false | When true, assistant replies are remembered too, not just user messages. |
var options = new AetherMemoryProviderOptions
{
K = 8,
MinRelevance = 0.5, // drop weak matches
RecencyWeight = 0.3, // lean slightly toward recent memories
IncludeAssistantMessages = true,
};
thread.AIContextProviders.Add(new AetherMemoryProvider(aether, entityId, options));
Limitations
- Semantic Kernel agent framework required. The provider plugs into
AIContextProvider, which is part of Semantic Kernel's agent framework and is marked experimental (diagnosticSKEXP0110). Add it toNoWarnas shown above. - .NET only. This memory provider is a .NET package. For memory from other languages, use the Memory API directly; for Semantic Kernel retrieval (available in Python too), see the retrieval guide.
- One provider, one entity. A provider instance is bound to a single entity id. Create a fresh provider per user or agent — don't reuse one instance across memory owners.
- Server-side fact extraction is opt-in.
ExtractFactsrequires fact extraction to be configured for your account; leave it off otherwise. - User turns carry the signal. By default only user messages are remembered. Turn on
IncludeAssistantMessagesif your agent's own replies contain durable facts worth recalling.
Next steps
- Semantic Kernel retrieval — ground answers in a document store with a kernel plugin.
- The Memory API — the entity-scoped
Memoryfacade the provider is built on, withremember,recall, and history. - Persistent memory in 5 minutes — the no-SDK path via MCP, for coding agents.