/**
 * Agent Memory Service
 *
 * Manages agent-level persistent memory stored in ~/.crewly/agents/{agentId}/
 * Provides storage for role knowledge, preferences, and performance metrics.
 *
 * @module services/memory/agent-memory.service
 */
import * as fs from 'fs/promises';
import * as path from 'path';
import { existsSync, mkdirSync } from 'fs';
import * as os from 'os';
import { v4 as uuidv4 } from 'uuid';
import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
import { isHiddenFromDefaultRecall } from './role-knowledge-eligibility.js';
/** Constants for memory scoring and decay (v2) */
const DECAY_CONSTANTS = {
    /** Recency tiers in days */
    RECENCY_TIERS_DAYS: { RECENT: 30, MEDIUM: 90, OLD: 180 },
    /** Score multipliers per recency tier */
    RECENCY_FACTORS: { RECENT: 1.0, MEDIUM: 0.75, OLD: 0.5, ANCIENT: 0.25 },
    /** Score multiplier for superseded entries */
    SUPERSEDED_PENALTY: 0.3,
    /** Score multiplier for entries never verified */
    UNVERIFIED_FACTOR: 0.8,
    /** Minimum effective score to be included in prompt injection */
    MIN_EFFECTIVE_SCORE: 0.4,
    /** Confidence threshold below which old entries are pruned */
    PRUNE_CONFIDENCE_THRESHOLD: 0.3,
    /** Confidence multiplier applied during decay */
    DECAY_MULTIPLIER: 0.5,
    /** Minimum confidence floor (never goes below this) */
    MIN_CONFIDENCE_FLOOR: 0.1,
    /** Milliseconds per day */
    MS_PER_DAY: 86_400_000,
};
import { DEFAULT_AGENT_MEMORY, } from '../../types/memory.types.js';
import { MEMORY_CONSTANTS, CREWLY_CONSTANTS } from '../../constants.js';
import { LoggerService } from '../core/logger.service.js';
/**
 * Service for managing agent-level persistent memory
 *
 * Follows singleton pattern for consistent state management.
 * Uses atomic file writes to prevent data corruption.
 *
 * @example
 * ```typescript
 * const memoryService = AgentMemoryService.getInstance();
 * await memoryService.initializeAgent('dev-001', 'developer');
 * await memoryService.addRoleKnowledge('dev-001', {
 *   category: 'best-practice',
 *   content: 'Always run tests before committing',
 *   confidence: 0.5
 * });
 * ```
 */
export class AgentMemoryService {
    static instance = null;
    static instanceHome = null;
    basePath;
    logger = LoggerService.getInstance().createComponentLogger('AgentMemoryService');
    // In-memory cache for frequently accessed data
    memoryCache = new Map();
    CACHE_TTL_MS = 30000; // 30 second cache
    /**
     * Creates a new AgentMemoryService instance
     *
     * @param crewlyHome - Optional custom path for crewly home directory
     */
    constructor(crewlyHome) {
        const homeDir = crewlyHome || path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME);
        this.basePath = path.join(homeDir, MEMORY_CONSTANTS.PATHS.AGENTS_DIR);
        this.ensureBaseDirectory();
    }
    /**
     * Gets the singleton instance of AgentMemoryService
     *
     * @param crewlyHome - Optional custom path for crewly home directory
     * @returns The singleton AgentMemoryService instance
     */
    static getInstance(crewlyHome) {
        const homeDir = crewlyHome || path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME);
        if (AgentMemoryService.instance && AgentMemoryService.instanceHome === homeDir) {
            return AgentMemoryService.instance;
        }
        AgentMemoryService.instance = new AgentMemoryService(homeDir);
        AgentMemoryService.instanceHome = homeDir;
        return AgentMemoryService.instance;
    }
    /**
     * Clears the singleton instance (useful for testing)
     */
    static clearInstance() {
        AgentMemoryService.instance = null;
        AgentMemoryService.instanceHome = null;
    }
    /**
     * Ensures the base agents directory exists
     */
    ensureBaseDirectory() {
        if (!existsSync(this.basePath)) {
            mkdirSync(this.basePath, { recursive: true });
            this.logger.debug('Created agents memory directory', { path: this.basePath });
        }
    }
    /**
     * Gets the path to an agent's memory directory
     *
     * @param agentId - The agent's unique identifier
     * @returns Absolute path to the agent's memory directory
     */
    getAgentPath(agentId) {
        return path.join(this.basePath, agentId);
    }
    /**
     * Gets the path to a specific memory file for an agent
     *
     * @param agentId - The agent's unique identifier
     * @param fileName - The memory file name
     * @returns Absolute path to the memory file
     */
    getFilePath(agentId, fileName) {
        return path.join(this.getAgentPath(agentId), fileName);
    }
    /**
     * Ensures an agent's directory structure exists
     *
     * @param agentId - The agent's unique identifier
     */
    async ensureAgentDirectory(agentId) {
        const agentPath = this.getAgentPath(agentId);
        await fs.mkdir(agentPath, { recursive: true });
        await fs.mkdir(path.join(agentPath, MEMORY_CONSTANTS.AGENT_FILES.SOP_CUSTOM_DIR), { recursive: true });
    }
    /**
     * Invalidates the cache for an agent
     *
     * @param agentId - The agent's unique identifier
     */
    invalidateCache(agentId) {
        this.memoryCache.delete(agentId);
    }
    /**
     * Gets cached agent memory or loads from disk
     *
     * @param agentId - The agent's unique identifier
     * @returns Agent memory or null if not found
     */
    async getCachedMemory(agentId) {
        const cached = this.memoryCache.get(agentId);
        if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) {
            return cached.data;
        }
        const memory = await this.loadAgentMemory(agentId);
        if (memory) {
            this.memoryCache.set(agentId, { data: memory, timestamp: Date.now() });
        }
        return memory;
    }
    /**
     * Loads agent memory from disk
     *
     * @param agentId - The agent's unique identifier
     * @returns Agent memory or null if not initialized
     */
    async loadAgentMemory(agentId) {
        const memoryPath = this.getFilePath(agentId, MEMORY_CONSTANTS.AGENT_FILES.MEMORY);
        return safeReadJson(memoryPath, null, this.logger);
    }
    /**
     * Saves agent memory to disk
     *
     * @param agentId - The agent's unique identifier
     * @param memory - Memory data to save
     */
    async saveAgentMemory(agentId, memory) {
        memory.updatedAt = new Date().toISOString();
        const memoryPath = this.getFilePath(agentId, MEMORY_CONSTANTS.AGENT_FILES.MEMORY);
        await atomicWriteJson(memoryPath, memory);
        this.invalidateCache(agentId);
    }
    /**
     * v3 (M4): Public narrow wrapper around {@link saveAgentMemory} for the
     * supersession service. The supersession service mutates entries in
     * place (sets `superseded`/`supersededBy`/extends `evidence`) and needs
     * to persist via the canonical write path so cache invalidation and
     * atomic-file semantics are honored.
     *
     * Do NOT broaden this surface — full memory replacement should still go
     * through reinforceKnowledge / addRoleKnowledge / etc. which guard against
     * concurrent-write surprises.
     *
     * @param agentId - Agent whose memory store to persist
     * @param memory - Full memory object (already migrated + mutated by caller)
     */
    async saveSupersededMemory(agentId, memory) {
        await this.saveAgentMemory(agentId, memory);
    }
    /**
     * Finds a similar existing knowledge entry to avoid duplicates
     *
     * @param knowledge - Existing knowledge entries
     * @param content - Content to check for similarity
     * @returns Matching entry or undefined
     */
    findSimilarEntry(knowledge, content) {
        const normalizedContent = content.toLowerCase().trim();
        return knowledge.find(entry => {
            const normalizedEntry = entry.content.toLowerCase().trim();
            // Simple similarity check - exact match or high substring overlap
            return normalizedEntry === normalizedContent ||
                (normalizedContent.length > 20 &&
                    (normalizedEntry.includes(normalizedContent) || normalizedContent.includes(normalizedEntry)));
        });
    }
    /**
     * Formats agent preferences for context output
     *
     * @param preferences - Agent preferences to format
     * @returns Formatted string
     */
    formatPreferences(preferences) {
        const lines = [];
        if (preferences.codingStyle) {
            if (preferences.codingStyle.language) {
                lines.push(`- Preferred language: ${preferences.codingStyle.language}`);
            }
            if (preferences.codingStyle.testingFramework) {
                lines.push(`- Testing framework: ${preferences.codingStyle.testingFramework}`);
            }
        }
        if (preferences.communicationStyle) {
            lines.push(`- Communication: ${preferences.communicationStyle.verbosity}`);
            if (preferences.communicationStyle.askBeforeAction) {
                lines.push(`- Ask before taking major actions`);
            }
        }
        if (preferences.workPatterns) {
            if (preferences.workPatterns.breakdownSize) {
                lines.push(`- Task breakdown: ${preferences.workPatterns.breakdownSize} chunks`);
            }
            if (preferences.workPatterns.commitFrequency) {
                lines.push(`- Commit frequency: ${preferences.workPatterns.commitFrequency}`);
            }
        }
        return lines.length > 0 ? lines.join('\n') : 'No specific preferences set';
    }
    // ========================= PUBLIC INTERFACE =========================
    /**
     * Initializes memory storage for a new agent
     *
     * @param agentId - Unique identifier for the agent
     * @param role - Agent's role (e.g., 'developer', 'qa', 'pm')
     *
     * @example
     * ```typescript
     * await memoryService.initializeAgent('frontend-dev-001', 'frontend-developer');
     * ```
     */
    async initializeAgent(agentId, role) {
        await this.ensureAgentDirectory(agentId);
        const existingMemory = await this.loadAgentMemory(agentId);
        if (existingMemory) {
            this.logger.debug('Agent already initialized', { agentId });
            return;
        }
        const now = new Date().toISOString();
        const memory = {
            agentId,
            role,
            createdAt: now,
            updatedAt: now,
            ...DEFAULT_AGENT_MEMORY,
        };
        await this.saveAgentMemory(agentId, memory);
        this.logger.info('Initialized agent memory', { agentId, role });
    }
    /**
     * Adds a new role knowledge entry
     *
     * If similar content already exists, reinforces the existing entry instead.
     *
     * @param agentId - Agent's unique identifier
     * @param entry - Knowledge entry (without id and createdAt)
     * @returns ID of the created or reinforced entry
     *
     * @example
     * ```typescript
     * const entryId = await memoryService.addRoleKnowledge('dev-001', {
     *   category: 'best-practice',
     *   content: 'Always validate user input before processing',
     *   confidence: 0.6,
     *   learnedFrom: 'TICKET-456'
     * });
     * ```
     */
    async addRoleKnowledge(agentId, entry) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            throw new Error(`Agent ${agentId} not initialized`);
        }
        // Check for similar existing entry
        const existing = this.findSimilarEntry(memory.roleKnowledge, entry.content);
        if (existing) {
            await this.reinforceKnowledge(agentId, existing.id);
            this.logger.debug('Reinforced existing knowledge entry', { agentId, entryId: existing.id });
            return existing.id;
        }
        // Enforce storage limits
        if (memory.roleKnowledge.length >= MEMORY_CONSTANTS.LIMITS.MAX_ROLE_KNOWLEDGE_ENTRIES) {
            // Remove lowest confidence entries
            memory.roleKnowledge.sort((a, b) => a.confidence - b.confidence);
            memory.roleKnowledge.shift();
            this.logger.debug('Removed lowest confidence entry to stay within limits', { agentId });
        }
        const newEntry = {
            ...entry,
            id: uuidv4(),
            createdAt: new Date().toISOString(),
            confidence: entry.confidence ?? MEMORY_CONSTANTS.DEFAULTS.INITIAL_CONFIDENCE,
        };
        memory.roleKnowledge.push(newEntry);
        await this.saveAgentMemory(agentId, memory);
        this.logger.info('Added role knowledge entry', { agentId, entryId: newEntry.id, category: newEntry.category });
        return newEntry.id;
    }
    /**
     * Gets role knowledge entries, optionally filtered by category
     *
     * @param agentId - Agent's unique identifier
     * @param category - Optional category filter
     * @returns Array of knowledge entries sorted by confidence (descending)
     */
    async getRoleKnowledge(agentId, category) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            return [];
        }
        let entries = memory.roleKnowledge;
        if (category) {
            entries = entries.filter(e => e.category === category);
        }
        return entries.sort((a, b) => b.confidence - a.confidence);
    }
    /**
     * Reinforces a knowledge entry by increasing its confidence
     *
     * @param agentId - Agent's unique identifier
     * @param entryId - ID of the entry to reinforce
     */
    async reinforceKnowledge(agentId, entryId) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            throw new Error(`Agent ${agentId} not initialized`);
        }
        const entry = memory.roleKnowledge.find(e => e.id === entryId);
        if (!entry) {
            this.logger.warn('Knowledge entry not found for reinforcement', { agentId, entryId });
            return;
        }
        entry.confidence = Math.min(entry.confidence + MEMORY_CONSTANTS.DEFAULTS.CONFIDENCE_REINFORCEMENT, MEMORY_CONSTANTS.DEFAULTS.MAX_CONFIDENCE);
        entry.lastUsed = new Date().toISOString();
        await this.saveAgentMemory(agentId, memory);
        this.logger.debug('Reinforced knowledge entry', { agentId, entryId, newConfidence: entry.confidence });
    }
    /**
     * Updates agent preferences (partial update supported)
     *
     * @param agentId - Agent's unique identifier
     * @param preferences - Partial preferences to update
     */
    async updatePreferences(agentId, preferences) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            throw new Error(`Agent ${agentId} not initialized`);
        }
        // Deep merge preferences - only include defined properties
        if (preferences.codingStyle) {
            memory.preferences.codingStyle = {
                ...memory.preferences.codingStyle,
                ...preferences.codingStyle,
            };
        }
        if (preferences.communicationStyle) {
            memory.preferences.communicationStyle = {
                ...memory.preferences.communicationStyle,
                ...preferences.communicationStyle,
            };
        }
        if (preferences.workPatterns) {
            memory.preferences.workPatterns = {
                ...memory.preferences.workPatterns,
                ...preferences.workPatterns,
            };
        }
        if (preferences.custom) {
            memory.preferences.custom = {
                ...memory.preferences.custom,
                ...preferences.custom,
            };
        }
        await this.saveAgentMemory(agentId, memory);
        this.logger.info('Updated agent preferences', { agentId });
    }
    /**
     * Gets the current agent preferences
     *
     * @param agentId - Agent's unique identifier
     * @returns Agent preferences or defaults
     */
    async getPreferences(agentId) {
        const memory = await this.getCachedMemory(agentId);
        return memory?.preferences ?? DEFAULT_AGENT_MEMORY.preferences;
    }
    /**
     * Records a task completion and updates performance metrics
     *
     * @param agentId - Agent's unique identifier
     * @param metrics - Task completion metrics
     */
    async recordTaskCompletion(agentId, metrics) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            throw new Error(`Agent ${agentId} not initialized`);
        }
        const perf = memory.performance;
        const totalTasks = perf.tasksCompleted + 1;
        // Update running averages
        perf.averageIterations = ((perf.averageIterations * perf.tasksCompleted) + metrics.iterations) / totalTasks;
        perf.qualityGatePassRate = ((perf.qualityGatePassRate * perf.tasksCompleted) + (metrics.qualityGatePassed ? 1 : 0)) / totalTasks;
        perf.tasksCompleted = totalTasks;
        if (metrics.completionTimeMinutes !== undefined) {
            const currentAvg = perf.averageCompletionTime ?? 0;
            perf.averageCompletionTime = ((currentAvg * (totalTasks - 1)) + metrics.completionTimeMinutes) / totalTasks;
        }
        // Track task type success rates
        if (metrics.taskType) {
            perf.taskTypeSuccessRates = perf.taskTypeSuccessRates ?? {};
            const currentRate = perf.taskTypeSuccessRates[metrics.taskType] ?? 0;
            const typeCount = Object.keys(perf.taskTypeSuccessRates).filter(t => t === metrics.taskType).length || 1;
            perf.taskTypeSuccessRates[metrics.taskType] = ((currentRate * typeCount) + (metrics.qualityGatePassed ? 1 : 0)) / (typeCount + 1);
        }
        await this.saveAgentMemory(agentId, memory);
        this.logger.info('Recorded task completion', { agentId, totalTasks, metrics });
    }
    /**
     * Records an error pattern encountered by the agent
     *
     * @param agentId - Agent's unique identifier
     * @param errorPattern - Description of the error pattern
     * @param resolution - Optional known resolution
     */
    async recordError(agentId, errorPattern, resolution) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            throw new Error(`Agent ${agentId} not initialized`);
        }
        const existing = memory.performance.commonErrors.find(e => e.pattern.toLowerCase() === errorPattern.toLowerCase());
        if (existing) {
            existing.occurrences++;
            existing.lastOccurred = new Date().toISOString();
            if (resolution) {
                existing.resolution = resolution;
            }
        }
        else {
            memory.performance.commonErrors.push({
                pattern: errorPattern,
                occurrences: 1,
                lastOccurred: new Date().toISOString(),
                resolution,
            });
        }
        // Keep only top 20 most frequent errors
        memory.performance.commonErrors.sort((a, b) => b.occurrences - a.occurrences);
        memory.performance.commonErrors = memory.performance.commonErrors.slice(0, 20);
        await this.saveAgentMemory(agentId, memory);
        this.logger.debug('Recorded error pattern', { agentId, errorPattern });
    }
    /**
     * Gets the current performance metrics for an agent
     *
     * @param agentId - Agent's unique identifier
     * @returns Performance metrics
     */
    async getPerformanceMetrics(agentId) {
        const memory = await this.getCachedMemory(agentId);
        return memory?.performance ?? DEFAULT_AGENT_MEMORY.performance;
    }
    /**
     * Generates a context string for prompt injection
     *
     * Compiles relevant agent memories, preferences, and performance data
     * into a formatted string suitable for inclusion in agent prompts.
     *
     * @param agentId - Agent's unique identifier
     * @returns Formatted context string
     */
    async generateAgentContext(agentId) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            return '';
        }
        const { roleKnowledge, preferences, performance } = memory;
        // v2: Filter using effective score (confidence × recency × verification)
        // Exclude performance memories — those are for TL/orchestrator scheduling, not self-use
        // M3 (spec §183-187): isHiddenFromDefaultRecall catches superseded
        // entries (both `superseded` flag AND `supersededBy` reference) plus
        // expired entries (ttl < now). Replaces the old `!k.superseded` check
        // which only saw the boolean flag.
        const scored = roleKnowledge
            .filter(k => !isHiddenFromDefaultRecall(k) && k.memoryType !== 'performance')
            .map(k => ({ entry: k, score: this.calculateEffectiveScore(k) }))
            .filter(s => s.score >= DECAY_CONSTANTS.MIN_EFFECTIVE_SCORE)
            .sort((a, b) => b.score - a.score)
            .slice(0, 20);
        // Group scored memories by type for structured injection
        const riskMemories = scored.filter(s => s.entry.memoryType === 'risk');
        const proceduralMemories = scored.filter(s => s.entry.memoryType === 'procedural' || !s.entry.memoryType);
        const domainMemories = scored.filter(s => s.entry.memoryType === 'domain');
        const preferenceMemories = scored.filter(s => s.entry.memoryType === 'preference');
        const formatEntry = (s) => {
            const k = s.entry;
            const outcomeHint = k.sourceOutcome === 'success' ? ' ✓'
                : k.sourceOutcome === 'failed' ? ' ✗'
                    : k.sourceOutcome === 'partial' ? ' ~'
                        : '';
            return `- [${k.category}] ${k.content}${outcomeHint}`;
        };
        const sections = [];
        // Risk memories first — these are warnings that should be seen early
        if (riskMemories.length > 0) {
            sections.push(`### ⚠ Risk Knowledge (may not be overridden by SOPs or preferences)

${riskMemories.map(formatEntry).join('\n')}`);
        }
        // Procedural memories — execution guidance
        if (proceduralMemories.length > 0) {
            sections.push(`### Procedural Knowledge

${proceduralMemories.map(formatEntry).join('\n')}`);
        }
        // Domain memories — flagged for freshness verification
        if (domainMemories.length > 0) {
            sections.push(`### Domain Knowledge (verify freshness before relying on these)

${domainMemories.map(formatEntry).join('\n')}`);
        }
        // Preference memories — lowest priority, may be refined by norms/SOPs
        if (preferenceMemories.length > 0) {
            sections.push(`### Preference Heuristics (lowest priority — may be overridden by team norms or SOPs)

${preferenceMemories.map(formatEntry).join('\n')}`);
        }
        const knowledgeSection = sections.length > 0
            ? `## Your Role Knowledge\n\n${sections.join('\n\n')}`
            : '';
        const preferencesSection = `## Your Preferences

${this.formatPreferences(preferences)}`;
        const performanceSection = `## Your Performance

- Tasks completed: ${performance.tasksCompleted}
- Average iterations: ${performance.averageIterations.toFixed(1)}
- Quality gate pass rate: ${(performance.qualityGatePassRate * 100).toFixed(0)}%`;
        const errorsSection = performance.commonErrors.length > 0
            ? `
### Common Errors to Avoid
${performance.commonErrors.slice(0, 5).map(e => `- ${e.pattern} → ${e.resolution || 'No resolution recorded'}`).join('\n')}`
            : '';
        return [knowledgeSection, preferencesSection, performanceSection + errorsSection]
            .filter(s => s.trim())
            .join('\n\n')
            .trim();
    }
    /**
     * Generate context for TL/orchestrator scheduling decisions.
     *
     * Returns performance memories for a specific agent — these are memories
     * about what an agent is good/bad at, intended for task assignment decisions.
     * Performance memories are NOT injected into the agent's own prompt.
     *
     * @param agentId - Agent whose performance memories to retrieve
     * @returns Formatted performance memory context for scheduling use
     */
    async generateSchedulingContext(agentId) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            return '';
        }
        const performanceMemories = memory.roleKnowledge
            // M3 (spec §183-187): hide superseded + expired entries from
            // scheduling context too — TL/orc must not schedule against stale
            // performance signals.
            .filter(k => !isHiddenFromDefaultRecall(k) && k.memoryType === 'performance')
            .map(k => ({ entry: k, score: this.calculateEffectiveScore(k) }))
            .filter(s => s.score >= DECAY_CONSTANTS.MIN_EFFECTIVE_SCORE)
            .sort((a, b) => b.score - a.score)
            .slice(0, 10);
        if (performanceMemories.length === 0) {
            return '';
        }
        return `### Agent Performance Profile (${agentId})

${performanceMemories.map(s => {
            const k = s.entry;
            const outcomeHint = k.sourceOutcome === 'success' ? ' ✓'
                : k.sourceOutcome === 'failed' ? ' ✗'
                    : k.sourceOutcome === 'partial' ? ' ~'
                        : '';
            return `- ${k.content}${outcomeHint}`;
        }).join('\n')}`;
    }
    /**
     * Prunes stale entries older than specified days
     *
     * Removes low-confidence entries that haven't been used recently.
     *
     * @param agentId - Agent's unique identifier
     * @param olderThanDays - Number of days threshold
     * @returns Number of entries removed
     */
    async pruneStaleEntries(agentId, olderThanDays) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory) {
            return 0;
        }
        const cutoffDate = new Date();
        cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
        const cutoffIso = cutoffDate.toISOString();
        const originalCount = memory.roleKnowledge.length;
        // Remove entries that are:
        // 1. Below minimum confidence threshold
        // 2. Haven't been used recently (or never used)
        // 3. Created before the cutoff date
        memory.roleKnowledge = memory.roleKnowledge.filter(entry => {
            const isHighConfidence = entry.confidence >= MEMORY_CONSTANTS.LIMITS.MIN_CONFIDENCE_THRESHOLD;
            const lastUsedDate = entry.lastUsed || entry.createdAt;
            const isRecent = lastUsedDate >= cutoffIso;
            return isHighConfidence || isRecent;
        });
        const removedCount = originalCount - memory.roleKnowledge.length;
        if (removedCount > 0) {
            await this.saveAgentMemory(agentId, memory);
            this.logger.info('Pruned stale knowledge entries', { agentId, removedCount });
        }
        return removedCount;
    }
    /**
     * Gets the complete agent memory object
     *
     * @param agentId - Agent's unique identifier
     * @returns Full agent memory or null if not initialized
     */
    async getAgentMemory(agentId) {
        return this.getCachedMemory(agentId);
    }
    // ========================= v2: Memory Scoring & Decay =========================
    /**
     * Calculate effective score for a knowledge entry.
     * Combines confidence, recency, and verification status.
     *
     * effectiveScore = confidence × recencyFactor × verificationFactor
     *
     * @param entry - The knowledge entry to score
     * @returns Effective score (0-1)
     */
    calculateEffectiveScore(entry) {
        const now = Date.now();
        const { RECENCY_TIERS_DAYS, RECENCY_FACTORS, SUPERSEDED_PENALTY, UNVERIFIED_FACTOR } = DECAY_CONSTANTS;
        // Recency: based on lastVerifiedAt, lastUsed, or createdAt
        const lastActive = entry.lastVerifiedAt || entry.lastUsed || entry.createdAt;
        const daysSinceActive = (now - new Date(lastActive).getTime()) / DECAY_CONSTANTS.MS_PER_DAY;
        const recencyFactor = daysSinceActive <= RECENCY_TIERS_DAYS.RECENT ? RECENCY_FACTORS.RECENT
            : daysSinceActive <= RECENCY_TIERS_DAYS.MEDIUM ? RECENCY_FACTORS.MEDIUM
                : daysSinceActive <= RECENCY_TIERS_DAYS.OLD ? RECENCY_FACTORS.OLD
                    : RECENCY_FACTORS.ANCIENT;
        // Verification: superseded entries heavily penalized
        const verificationFactor = entry.superseded ? SUPERSEDED_PENALTY
            : entry.lastVerifiedAt ? 1.0
                : UNVERIFIED_FACTOR;
        return entry.confidence * recencyFactor * verificationFactor;
    }
    /**
     * Run decay sweep: halve confidence of entries unused for 90+ days.
     * Remove entries unused for 180+ days with very low confidence.
     * Skip entries with lastVerifiedAt in last 30 days (recently validated).
     *
     * @param agentId - Agent's unique identifier
     * @returns Count of decayed and pruned entries
     */
    async runDecaySweep(agentId) {
        const memory = await this.getCachedMemory(agentId);
        if (!memory)
            return { decayed: 0, pruned: 0 };
        const now = Date.now();
        let decayed = 0;
        let pruned = 0;
        const { RECENCY_TIERS_DAYS, PRUNE_CONFIDENCE_THRESHOLD, DECAY_MULTIPLIER, MIN_CONFIDENCE_FLOOR, MS_PER_DAY } = DECAY_CONSTANTS;
        memory.roleKnowledge = memory.roleKnowledge.filter(entry => {
            // Skip if recently verified
            if (entry.lastVerifiedAt) {
                const verifiedDaysAgo = (now - new Date(entry.lastVerifiedAt).getTime()) / MS_PER_DAY;
                if (verifiedDaysAgo <= RECENCY_TIERS_DAYS.RECENT)
                    return true;
            }
            const lastActive = entry.lastUsed || entry.createdAt;
            const daysInactive = (now - new Date(lastActive).getTime()) / MS_PER_DAY;
            if (daysInactive > RECENCY_TIERS_DAYS.OLD && entry.confidence < PRUNE_CONFIDENCE_THRESHOLD) {
                pruned++;
                return false; // remove
            }
            if (daysInactive > RECENCY_TIERS_DAYS.MEDIUM) {
                entry.confidence = Math.max(MIN_CONFIDENCE_FLOOR, entry.confidence * DECAY_MULTIPLIER);
                decayed++;
            }
            return true;
        });
        if (decayed > 0 || pruned > 0) {
            await this.saveAgentMemory(agentId, memory);
            this.logger.info('Decay sweep completed', { agentId, decayed, pruned });
        }
        return { decayed, pruned };
    }
}
//# sourceMappingURL=agent-memory.service.js.map