Long-Term Episode Retention
Episodic memory accumulates over time. Octopus provides configurable TTL-based retention, archival of old episodes (keeping summaries while removing full message content), and a background job that enforces the retention policy.
Retention Policy Configuration
// Per-agent retention policy
public class EpisodicRetentionPolicy
{
public int ActiveRetentionDays { get; set; } = 90; // full data kept 90 days
public int ArchiveRetentionDays { get; set; } = 365; // summaries kept 1 year
public bool ArchiveOnExpiry { get; set; } = true; // archive vs. delete
public bool DeleteAfterArchivalPeriod { get; set; } = true; // hard delete after 1yr
}
Retention Lifecycle
Active (0–90 days)
Full episode data — messages, tool calls, embedding. All recall modes available including message-level retrieval.
Archived (90–365 days)
MessagesJson and ToolCallsJson are cleared. Only Summary and SummaryEmbedding retained. Semantic recall still works; message-level recall returns "archived".
Deleted (>365 days)
Row is deleted from the table. No recall possible. Summaries are no longer available.
MemoryRetentionJob
// Runs nightly as a background service
public class MemoryRetentionJob : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var cutoffArchive = DateTimeOffset.UtcNow.AddDays(-_policy.ActiveRetentionDays);
var cutoffDelete = DateTimeOffset.UtcNow.AddDays(-_policy.ArchiveRetentionDays);
// Archive: clear message content, keep summary
await _db.Episodes
.Where(e => e.EndedAt < cutoffArchive && !e.IsArchived)
.ExecuteUpdateAsync(s => s
.SetProperty(e => e.MessagesJson, "[]")
.SetProperty(e => e.ToolCallsJson, "[]")
.SetProperty(e => e.IsArchived, true));
// Delete: hard delete very old episodes
await _db.Episodes
.Where(e => e.EndedAt < cutoffDelete && e.IsArchived)
.ExecuteDeleteAsync();
await Task.Delay(TimeSpan.FromHours(24), ct);
}
}
}
Cross-Session Semantic Recall after Archival
Even after a session is archived (messages cleared), the summary embedding remains. The agent can still "remember" that a topic was discussed without knowing the exact content — the summary provides sufficient context for recall:
// A recalled archived episode looks like:
new EpisodeSnippet {
EpisodeId = Guid.Parse("..."),
Summary = "User inquired about annual leave balance for 2025. Agent confirmed 12 days remaining.",
Date = new DateTimeOffset(2025, 3, 14, ...),
RelevanceScore = 0.87f,
IsArchived = true // signals that full message content is unavailable
}