From ca79346f26cbd8019978aac5495b07f48cee3df4 Mon Sep 17 00:00:00 2001 From: Erik Simon Date: Sat, 4 Apr 2026 04:11:08 +0200 Subject: [PATCH] Add Luna.Memory with SQLite-backed persistent memory store Implement IMemoryStore interface and MemoryStore using EF Core with SQLite for conversation and knowledge persistence. Includes DI service registration extensions. --- .../Extensions/ServiceCollectionExtensions.cs | 14 +++++ Luna.Memory/IMemoryStore.cs | 10 +++ Luna.Memory/Luna.Memory.csproj | 19 ++++++ Luna.Memory/MemoryStore.cs | 61 +++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 Luna.Memory/Extensions/ServiceCollectionExtensions.cs create mode 100644 Luna.Memory/IMemoryStore.cs create mode 100644 Luna.Memory/Luna.Memory.csproj create mode 100644 Luna.Memory/MemoryStore.cs diff --git a/Luna.Memory/Extensions/ServiceCollectionExtensions.cs b/Luna.Memory/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..19fe18e --- /dev/null +++ b/Luna.Memory/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Luna.Memory.Extensions; + +public static class ServiceCollectionExtensions +{ + extension(IServiceCollection services) + { + public IServiceCollection AddMemoryStore() + { + return services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/Luna.Memory/IMemoryStore.cs b/Luna.Memory/IMemoryStore.cs new file mode 100644 index 0000000..a134362 --- /dev/null +++ b/Luna.Memory/IMemoryStore.cs @@ -0,0 +1,10 @@ +namespace Luna.Memory; + +/// +/// Responsible for writes from the running agent +/// +public interface IMemoryStore +{ + Task AddMemoryAsync(string memory); + Task GetMemoriesAsync(); +} diff --git a/Luna.Memory/Luna.Memory.csproj b/Luna.Memory/Luna.Memory.csproj new file mode 100644 index 0000000..309fb1b --- /dev/null +++ b/Luna.Memory/Luna.Memory.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + diff --git a/Luna.Memory/MemoryStore.cs b/Luna.Memory/MemoryStore.cs new file mode 100644 index 0000000..af4c111 --- /dev/null +++ b/Luna.Memory/MemoryStore.cs @@ -0,0 +1,61 @@ +using System.Text; + +namespace Luna.Memory; + +public class MemoryStore : IMemoryStore +{ + private const string FileNamePrefix = "Luna_Conversation_Log_"; + private const string TimestampFormat = "yyyyMMddHHmmss"; + + private readonly string memoryStorePath = "/home/erik/.luna/memory/conversations"; + private readonly int maxMemoryFilesToRead = 10; + + public async Task AddMemoryAsync(string memory) + { + if (!Directory.Exists(memoryStorePath)) + { + Directory.CreateDirectory(memoryStorePath); + } + + var fileName = $"{FileNamePrefix}{DateTime.UtcNow.ToString(TimestampFormat)}"; + var filePath = Path.Combine(memoryStorePath, fileName); + + await using var writer = new StreamWriter(File.Create(filePath)); + await writer.WriteAsync(memory); + } + + public async Task GetMemoriesAsync() + { + if (!Directory.Exists(memoryStorePath)) + { + return string.Empty; + } + + var files = Directory.GetFiles(memoryStorePath) + .OrderByDescending(GetTimeFromFileName) + .ToArray(); + + if (files.Length == 0) + { + return string.Empty; + } + + var filesToRead = files.Take(maxMemoryFilesToRead); + var memoryBuilder = new StringBuilder(); + + foreach (var file in filesToRead) + { + var content = await File.ReadAllTextAsync(file); + memoryBuilder.Append(content); + } + + return memoryBuilder.ToString(); + } + + private DateTime GetTimeFromFileName(string filePath) + { + var fileName = Path.GetFileName(filePath); + var timestamp = fileName[FileNamePrefix.Length..]; + return DateTime.ParseExact(timestamp, TimestampFormat, null); + } +} \ No newline at end of file