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.
This commit is contained in:
2026-04-04 04:11:08 +02:00
parent bc25dc7b07
commit ca79346f26
4 changed files with 104 additions and 0 deletions
@@ -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<IMemoryStore, MemoryStore>();
}
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Luna.Memory;
/// <summary>
/// Responsible for writes from the running agent
/// </summary>
public interface IMemoryStore
{
Task AddMemoryAsync(string memory);
Task<string> GetMemoriesAsync();
}
+19
View File
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.5" />
<PackageReference Include="StackExchange.Redis" Version="2.12.8" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
</ItemGroup>
</Project>
+61
View File
@@ -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<string> 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);
}
}