MemoryManagerService
Layer 2 service that manages context lifecycle and memory tier classification.
Overview
MemoryManagerService is the implementation of Layer 2 (Memory Manager). It tracks HOW relevant contexts are right now by classifying them into memory tiers based on access patterns and age.
Location: src/domain/services/MemoryManagerService.ts
Layer: Layer 2 - Present (HOW)
Purpose: Manage context relevance and lifecycle
Constructor
constructor(private readonly repository: IContextRepository)Parameters:
repository: Database abstraction for context storage
Methods
calculateMemoryTier(lastAccessed: Date | null, now: Date): MemoryTier
Determine memory tier based on time since last access.
Parameters:
lastAccessed: When context was last retrieved (null = never accessed)now: Current timestamp
Returns: MemoryTier enum value
Algorithm:
const hoursSinceAccess = (now - lastAccessed) / (1000 * 60 * 60);
if (hoursSinceAccess < 1) return MemoryTier.ACTIVE;
if (hoursSinceAccess < 24) return MemoryTier.RECENT;
if (hoursSinceAccess < 24 * 30) return MemoryTier.ARCHIVED;
return MemoryTier.EXPIRED;Example:
const tier = memoryManager.calculateMemoryTier(
new Date('2025-10-17T14:00:00Z'),
new Date('2025-10-17T14:30:00Z')
);
// Returns: MemoryTier.ACTIVE (30 minutes old)Tier Thresholds:
- ACTIVE: < 1 hour (hot, frequently accessed)
- RECENT: 1-24 hours (warm, recently used)
- ARCHIVED: 1-30 days (cold, aging)
- EXPIRED: > 30 days (frozen, pruning candidate)
getMemoryStats(project: string): Promise<MemoryStats>
Get aggregate statistics on memory tier distribution for a project.
Parameters:
project: Project to analyze
Returns: Statistics object
Return Type:
interface MemoryStats {
total: number;
active: number;
recent: number;
archived: number;
expired: number;
}Example:
const stats = await memoryManager.getMemoryStats('my-project');
// Returns:
// {
// total: 48,
// active: 5,
// recent: 12,
// archived: 28,
// expired: 3
// }Implementation:
async getMemoryStats(project: string): Promise<MemoryStats> {
const contexts = await this.repository.findByProject(project, 1000);
const stats = { total: contexts.length, active: 0, recent: 0, archived: 0, expired: 0 };
for (const context of contexts) {
switch (context.memory?.tier) {
case MemoryTier.ACTIVE: stats.active++; break;
case MemoryTier.RECENT: stats.recent++; break;
case MemoryTier.ARCHIVED: stats.archived++; break;
case MemoryTier.EXPIRED: stats.expired++; break;
}
}
return stats;
}trackAccess(snapshotId: string): Promise<void>
Update access tracking metadata when a context is retrieved.
Parameters:
snapshotId: ID of context being accessed
Side Effects:
- Updates
lastAccessedto current timestamp - Increments
accessCount - May trigger tier recalculation
Example:
// When loading a context
const context = await repository.findById(id);
await memoryManager.trackAccess(id); // Update LRU metadataDatabase Update:
UPDATE context_snapshots
SET last_accessed = datetime('now'),
access_count = access_count + 1
WHERE id = ?recalculateAllTiers(project?: string): Promise<number>
Recalculate memory tiers for all contexts (or specific project).
Parameters:
project: Optional project filter (if omitted, processes all projects)
Returns: Number of contexts updated
Example:
// Recalculate for one project
const updated = await memoryManager.recalculateAllTiers('my-project');
console.log(`Updated ${updated} contexts`);
// Recalculate for all projects
const updated = await memoryManager.recalculateAllTiers();Use Cases:
- Scheduled maintenance (daily/weekly)
- After long periods of inactivity
- Before querying by tier
Performance: O(n) where n = context count, batched for efficiency
pruneExpiredContexts(limit?: number): Promise<number>
Delete expired contexts that are no longer needed.
Parameters:
limit: Optional maximum contexts to delete (for safety)
Returns: Number of contexts deleted
Example:
// Prune up to 50 expired contexts
const deleted = await memoryManager.pruneExpiredContexts(50);
console.log(`Deleted ${deleted} expired contexts`);
// Prune all expired contexts (use with caution)
const deleted = await memoryManager.pruneExpiredContexts();Safety:
- Only deletes contexts in EXPIRED tier
- Respects limit parameter
- Idempotent (safe to run multiple times)
Query:
DELETE FROM context_snapshots
WHERE memory_tier = 'expired'
ORDER BY last_accessed ASC NULLS FIRST
LIMIT ?findLeastRecentlyUsed(tier: MemoryTier, limit: number): Promise<IContextSnapshot[]>
Find least recently used contexts in a specific tier.
Parameters:
tier: Memory tier to search withinlimit: Maximum contexts to return
Returns: Array of contexts sorted by access time (oldest first)
Example:
// Find oldest archived contexts
const lru = await memoryManager.findLeastRecentlyUsed(
MemoryTier.ARCHIVED,
10
);
// These are candidates for archival or pruningUse Cases:
- Identify contexts to move to lower tier
- Find candidates for pruning
- Analyze access patterns
Types
MemoryTier
enum MemoryTier {
ACTIVE = 'active', // < 1 hour since access
RECENT = 'recent', // 1-24 hours
ARCHIVED = 'archived', // 1-30 days
EXPIRED = 'expired' // > 30 days
}MemoryStats
interface MemoryStats {
total: number; // Total contexts
active: number; // Count in ACTIVE tier
recent: number; // Count in RECENT tier
archived: number; // Count in ARCHIVED tier
expired: number; // Count in EXPIRED tier
}Usage Examples
Monitor Memory Health
const stats = await memoryManager.getMemoryStats('my-project');
// Alert if too many expired
if (stats.expired > 100) {
console.warn('High expired count - consider pruning');
await memoryManager.pruneExpiredContexts(50);
}
// Alert if no activity
if (stats.active === 0 && stats.recent === 0) {
console.warn('Project appears inactive');
}Scheduled Maintenance
// Daily tier recalculation
cron.schedule('0 0 * * *', async () => {
const updated = await memoryManager.recalculateAllTiers();
console.log(`Recalculated ${updated} context tiers`);
});
// Weekly pruning
cron.schedule('0 0 * * 0', async () => {
const deleted = await memoryManager.pruneExpiredContexts(100);
console.log(`Pruned ${deleted} expired contexts`);
});Context Loading with Tracking
async function loadContextWithTracking(id: string): Promise<IContextSnapshot> {
const context = await repository.findById(id);
if (context) {
// Update access metadata
await memoryManager.trackAccess(id);
// Optionally recalculate tier
const tier = memoryManager.calculateMemoryTier(
context.memory?.lastAccessed || null,
new Date()
);
if (tier !== context.memory?.tier) {
// Tier changed - update in database
await repository.updateTier(id, tier);
}
}
return context;
}Best Practices
1. Always Track Access
// ✅ Good: Track every load
const context = await repository.findById(id);
await memoryManager.trackAccess(id);
// ❌ Bad: Load without tracking
const context = await repository.findById(id);
// LRU data becomes stale2. Regular Tier Recalculation
// ✅ Good: Scheduled updates
cron.schedule('0 * * * *', async () => {
await memoryManager.recalculateAllTiers();
});
// ❌ Bad: Never recalculating
// Contexts stay in wrong tiers3. Safe Pruning
// ✅ Good: Limit pruning
await memoryManager.pruneExpiredContexts(100);
// ⚠️ Risky: Unlimited pruning
await memoryManager.pruneExpiredContexts();
// Could delete thousands of contextsTesting
Unit Test Example
describe('MemoryManagerService', () => {
it('calculates ACTIVE tier for recent access', () => {
const service = new MemoryManagerService(mockRepo);
const lastAccessed = new Date('2025-10-17T14:00:00Z');
const now = new Date('2025-10-17T14:30:00Z');
const tier = service.calculateMemoryTier(lastAccessed, now);
expect(tier).toBe(MemoryTier.ACTIVE);
});
it('calculates EXPIRED tier for old contexts', () => {
const service = new MemoryManagerService(mockRepo);
const lastAccessed = new Date('2024-09-01T00:00:00Z');
const now = new Date('2025-10-17T00:00:00Z');
const tier = service.calculateMemoryTier(lastAccessed, now);
expect(tier).toBe(MemoryTier.EXPIRED);
});
});Integration with Other Layers
With Layer 1 (Causality)
Memory tier affects causal chain importance:
- ACTIVE contexts are likely involved in current work
- ARCHIVED contexts are historical reference
With Layer 3 (Propagation)
Memory tier is a key factor in prediction scoring:
- ACTIVE tier: +0.3 score boost
- RECENT tier: +0.1 score boost
- EXPIRED tier: -0.2 score penalty
See Also
- API Overview - Service architecture
- ContextSnapshot - Entity with memory metadata
- Layer 2: Memory Manager - Concepts and design
- get_memory_stats tool - MCP tool usage
- Database Schema - Memory tier columns
