A C# implementation of the General Agentic Memory (GAM) framework for building AI agents with long-term memory capabilities.
This project is a .NET implementation based on the research and concepts from:
- Paper: General Agentic Memory for Large Language Model Agents - The foundational research describing the GAM framework and JIT memory retrieval paradigm.
- Original Implementation: VectorSpaceLab/general-agentic-memory - The original Python implementation by VectorSpaceLab.
GAM (General Agentic Memory) is a memory system for AI agents that uses a JIT (Just-in-Time) compilation paradigm. Instead of pre-processing all memories into a fixed format, GAM stores raw conversation data and dynamically retrieves relevant context at query time.
| Concept | Description |
|---|---|
| Page | A unit of memory storage containing raw conversation content |
| Abstract | A structured summary of a page with searchable headers |
| MemoryAgent | Offline agent that processes conversations into pages + abstracts |
| ResearchAgent | Online agent that retrieves relevant memories via iterative research |
| JIT Retrieval | Dynamic, query-time memory retrieval (vs. pre-compiled summaries) |
┌─────────────────────────────────────────────────────────────────┐
│ MEMORIZE (Offline) │
├─────────────────────────────────────────────────────────────────┤
│ Conversation ┌──────────────┐ ┌─────────┐ ┌─────────┐ │
│ Turn ──────────▶│ MemoryAgent │───▶│ Abstract│ + │ Page │ │
│ └──────────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ (LLM call) (headers) (raw content) │
│ ▼ ▼ │
│ ┌─────────────────────┐ │
│ │ Memory Store │ │
│ │ (PostgreSQL) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ RESEARCH (Online) │
├─────────────────────────────────────────────────────────────────┤
│ Query ──────────▶┌───────────────┐ │
│ │ ResearchAgent │◀─────────────────┐ │
│ └───────┬───────┘ │ │
│ │ │ │
│ ┌──────────────┼──────────────┐ │ │
│ ▼ ▼ ▼ │ │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ Plan │───▶│ Search │──▶│Integrate │─────┘ │
│ └────────┘ └──────────┘ └──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ │
│ │ Retrievers│ │ Context │ │
│ │(BM25/Vec) │ │ Builder │ │
│ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────────┘
# Core library (no external dependencies)
dotnet add package Gam.Core
# PostgreSQL storage with pgvector
dotnet add package Gam.Storage.Postgres
# OpenAI/Azure OpenAI provider
dotnet add package Gam.Providers.OpenAI
# Ollama provider (local inference)
dotnet add package Gam.Providers.Ollamausing Gam.Core;
using Gam.Core.Models;
using Gam.Providers.OpenAI;
using Gam.Storage.Postgres;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddLogging();
services.AddGamCore();
services.AddGamPostgresStorage("Host=localhost;Database=gam");
services.AddGamOpenAI("sk-your-api-key");
var provider = services.BuildServiceProvider();
var gam = provider.GetRequiredService<IGamService>();
// Store a memory
await gam.MemorizeAsync(new MemorizeRequest
{
Turn = new ConversationTurn
{
OwnerId = "user-123",
UserMessage = "How do I configure Kubernetes health checks?",
AssistantMessage = "You can configure liveness and readiness probes...",
Timestamp = DateTimeOffset.UtcNow
}
});
// Research memories
var context = await gam.ResearchAsync(new ResearchRequest
{
OwnerId = "user-123",
Query = "What did we discuss about Kubernetes?"
});
Console.WriteLine($"Found {context.Pages.Count} relevant memories");
Console.WriteLine(context.FormatForPrompt());GAM.NET requires PostgreSQL with the pgvector extension for vector similarity search.
For better keyword search (true BM25 ranking), you can install one of these extensions:
| Extension | License | Status | Notes |
|---|---|---|---|
| pg_textsearch | PostgreSQL | Pre-release | Most permissive license |
| ParadeDB pg_search | AGPLv3 | Production | Most mature, Elasticsearch alternative |
| VectorChord-bm25 | AGPLv3/ELv2 | v0.3.0 | Requires separate tokenizer |
If no BM25 extension is installed, GAM.NET falls back to native PostgreSQL full-text search (ts_rank).
-- Enable required extension
CREATE EXTENSION IF NOT EXISTS vector;
-- Create tables
CREATE TABLE memory_pages (
id UUID PRIMARY KEY,
owner_id VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
token_count INTEGER NOT NULL,
embedding vector(1536),
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE memory_abstracts (
page_id UUID PRIMARY KEY REFERENCES memory_pages(id) ON DELETE CASCADE,
owner_id VARCHAR(255) NOT NULL,
summary TEXT NOT NULL,
headers TEXT[] NOT NULL,
summary_embedding vector(1536),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Create indexes
CREATE INDEX idx_pages_owner ON memory_pages(owner_id);
CREATE INDEX idx_pages_embedding ON memory_pages USING hnsw (embedding vector_cosine_ops);
CREATE INDEX idx_pages_content_fts ON memory_pages USING gin(to_tsvector('english', content));See src/Gam.Storage.Postgres/Migrations/001_InitialSchema.sql for the complete schema.
services.AddGamOpenAI(
apiKey: "sk-...",
model: "gpt-4o",
embeddingModel: "text-embedding-3-small"
);services.AddGamAzureOpenAI(
endpoint: "https://your-resource.openai.azure.com/",
apiKey: "your-key",
chatDeployment: "gpt-4o",
embeddingDeployment: "text-embedding-3-small"
);services.AddGamOllama(
baseUrl: "http://localhost:11434",
llmModel: "llama3.2",
embeddingModel: "nomic-embed-text"
);gam-dotnet/
├── src/
│ ├── Gam.Core/ # Core abstractions and agents
│ ├── Gam.Storage.Postgres/ # PostgreSQL + pgvector
│ ├── Gam.Providers.OpenAI/ # OpenAI / Azure OpenAI
│ └── Gam.Providers.Ollama/ # Local Ollama
├── tests/
│ ├── Gam.Core.Tests/
│ └── Gam.Integration.Tests/
├── samples/
│ ├── Gam.Sample.Console/
│ └── Gam.Sample.WebApi/
└── Gam.sln
For long-running applications, you can enable automatic cleanup of old memories:
// Add TTL with 30-day expiration
services.AddGamMemoryTtl(TimeSpan.FromDays(30));
// Or with full configuration
services.AddGamMemoryTtl(options =>
{
options.Enabled = true;
options.MaxAge = TimeSpan.FromDays(30);
options.CleanupInterval = TimeSpan.FromHours(1);
options.OwnerIds = null; // Cleanup all owners, or specify specific ones
});You can also manually clean up expired memories:
var store = provider.GetRequiredService<IMemoryStore>();
// Delete memories older than 30 days
var deleted = await store.CleanupExpiredAsync(TimeSpan.FromDays(30));
// Delete memories before a specific date
await store.DeleteBeforeAsync(DateTimeOffset.UtcNow.AddDays(-30));
// Delete only for a specific owner
await store.CleanupExpiredAsync(TimeSpan.FromDays(30), ownerId: "user-123");GAM.NET supports typed configuration via appsettings.json:
{
"Gam": {
"Provider": "OpenAI",
"OpenAI": {
"ApiKey": "sk-...",
"Model": "gpt-4o",
"EmbeddingModel": "text-embedding-3-small",
"EmbeddingDimensions": 1536
},
"Research": {
"MaxIterations": 5,
"MaxPagesPerIteration": 10,
"MaxContextTokens": 8000,
"MinRelevanceScore": 0.3
},
"Storage": {
"ConnectionString": "Host=localhost;Database=gam",
"EmbeddingDimensions": 1536
},
"Ttl": {
"Enabled": true,
"MaxAgeDays": 30,
"CleanupIntervalHours": 1
},
"Prompts": {
"PromptDirectory": "/path/to/prompts",
"ContentPreviewLength": 200,
"MaxPagesInPlanPrompt": 5,
"MaxPagesInReflectPrompt": 7
}
}
}var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGamCore(builder.Configuration);
builder.Services.AddGamPostgresStorage(builder.Configuration);
builder.Services.AddGamOpenAI(builder.Configuration);GAM.NET allows you to customize the prompts used by agents for memory generation and research.
Override prompts directly in appsettings.json:
{
"Gam": {
"Prompts": {
"MemorySystemPrompt": "You are a librarian for a software development team...",
"PlanSystemPrompt": "You are a research assistant. Focus on code-related memories...",
"ReflectSystemPrompt": "Evaluate if the context is sufficient for answering..."
}
}
}Store prompts in external files for easier editing:
{
"Gam": {
"Prompts": {
"PromptDirectory": "/app/prompts"
}
}
}Create files in the directory:
memory_system.txt- System prompt for memory abstractionplan_system.txt- System prompt for research planningreflect_system.txt- System prompt for context evaluation
Implement IPromptProvider for full control:
public class MyPromptProvider : IPromptProvider
{
public string GetMemorySystemPrompt() => "Your custom prompt...";
public string BuildMemoryUserPrompt(ConversationTurn turn)
{
return $"Summarize this: {turn.UserMessage}";
}
// ... implement other methods
}
// Register your provider
services.AddSingleton<IPromptProvider, MyPromptProvider>();| Option | Default | Description |
|---|---|---|
PromptDirectory |
null | Directory containing prompt template files |
MemorySystemPrompt |
null | Override the memory agent system prompt |
PlanSystemPrompt |
null | Override the research plan prompt |
ReflectSystemPrompt |
null | Override the research reflect prompt |
IncludeToolCalls |
true | Include tool call details in memory prompts |
IncludeConversationId |
true | Include conversation ID in memory prompts |
ContentPreviewLength |
200 | Max chars of content preview in prompts |
MaxPagesInPlanPrompt |
5 | Max pages shown in plan prompt |
MaxPagesInReflectPrompt |
7 | Max pages shown in reflect prompt |
GAM.NET provides OpenAI-compatible tool schemas for integration with AI frameworks like Vercel AI SDK, LangChain, or direct OpenAI function calling.
using Gam.Core.Tools;
// Get tool definitions in OpenAI format
var tools = GamToolSchemas.GetAllTools();
// Or as JSON
var json = GamToolSchemas.ToJson(indented: true);| Tool | Description |
|---|---|
gam_memorize |
Store a conversation turn in long-term memory |
gam_research |
Search memories for relevant context |
gam_forget |
Delete memories for a user |
using Gam.Core.Tools;
var handler = new GamToolHandler(gamService);
// Execute a tool call from an AI model
var result = await handler.ExecuteAsync(
"gam_research",
"""{"owner_id": "user-123", "query": "What did we discuss about Kubernetes?"}"""
);
if (result.Success)
{
Console.WriteLine(result.Content); // Memory context for the model
}The WebApi sample exposes these endpoints:
# Get tool schemas
GET /tools
# Execute any tool
POST /tools/execute
{ "name": "gam_research", "arguments": "{\"owner_id\": \"user-123\", \"query\": \"...\"}" }
# Vercel AI SDK compatible
POST /v1/tools/research
{ "owner_id": "user-123", "query": "..." }import { generateText, tool } from 'ai';
const result = await generateText({
model: openai('gpt-4o'),
tools: {
gam_research: tool({
description: 'Search long-term memory for relevant context',
parameters: z.object({
owner_id: z.string(),
query: z.string(),
}),
execute: async ({ owner_id, query }) => {
const res = await fetch('http://localhost:5000/v1/tools/research', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ owner_id, query }),
});
return res.json();
},
}),
},
prompt: 'What did the user previously ask about Kubernetes?',
});This project is licensed under the MIT License - see the LICENSE file for details.
- GAM Paper - General Agentic Memory for Large Language Model Agents
- Original Implementation - Python implementation by VectorSpaceLab
- pgvector - Vector similarity search for PostgreSQL