/** * Memory Service Coordinator * * Provides a unified interface for both agent-level and project-level memory services. * Handles cross-cutting concerns like logging, validation, and combined context generation. * * @module services/memory/memory.service */ import { IAgentMemoryService } from './agent-memory.service.js'; import { IProjectMemoryService } from './project-memory.service.js'; import type { PatternCategory, GotchaSeverity, TaskHistoryEntry } from '../../types/memory.types.js'; /** * Categories for the unified remember operation */ export type RememberCategory = 'fact' | 'pattern' | 'decision' | 'gotcha' | 'preference' | 'user_preference' | 'relationship'; /** * Scope for memory operations */ export type MemoryScope = 'agent' | 'project' | 'both'; /** * Parameters for the remember operation */ export interface RememberParams { /** Agent identifier */ agentId: string; /** Project path (required for project scope) */ projectPath?: string; /** Content to remember */ content: string; /** Category of memory */ category: RememberCategory; /** Scope: agent or project */ scope: 'agent' | 'project'; /** Additional metadata */ metadata?: { /** Task ID where this was learned */ taskId?: string; /** Title for patterns/decisions/gotchas */ title?: string; /** Pattern category */ patternCategory?: PatternCategory; /** Code example */ example?: string; /** Related files */ files?: string[]; /** Rationale for decisions */ rationale?: string; /** Alternatives considered for decisions */ alternatives?: string[]; /** Areas affected by decisions */ affectedAreas?: string[]; /** Solution for gotchas */ solution?: string; /** Severity for gotchas */ severity?: GotchaSeverity; /** Relationship type */ relationshipType?: 'depends-on' | 'uses' | 'extends' | 'implements' | 'calls' | 'imported-by'; /** Target component for relationships */ targetComponent?: string; /** Task ID where this knowledge was learned */ sourceTaskId?: string; /** Objective/goal ID this knowledge relates to */ sourceObjectiveId?: string; /** Outcome of the task where this was learned */ sourceOutcome?: string; /** What contexts/domains this knowledge applies to */ appliesTo?: string[]; /** * Id(s) of entries this one supersedes. For project decisions each named * decision is marked superseded (#884); bare or `dec:`-prefixed ids, a * comma-separated string or an array. */ supersedes?: string | string[]; }; } /** * Parameters for the recall operation */ export interface RecallParams { /** Agent identifier */ agentId: string; /** Project path (required for project/both scope) */ projectPath?: string; /** Reader's role — wiki pages whose `visibility` excludes it are hidden (undefined = owner). */ viewerRole?: string; /** Context/query for finding relevant memories */ context: string; /** Scope: agent, project, or both */ scope: MemoryScope; /** Maximum number of results */ limit?: number; /** * v3 (M4 — NOTE-A): Include entries that are normally hidden from default * recall (superseded or TTL-expired entries). Default `false`. * * Use this to surface audit-only memories — e.g. when a TL is reviewing * what an agent learned before a fact was superseded, or when a debugging * tool needs the full trail. Production prompt-injection paths must keep * this `false` (or unset) so superseded entries do not pollute context. */ includeHidden?: boolean; /** * Optional capability filter. When set, recall queries the project's * task-history ledger for entries whose `capabilities[]` contains an * exact match. The matching entries surface on * {@link RecallResult.taskHistory} sorted most-recent first. * * This is the load-bearing field for "who in my team has done X?" * — the orchestrator's delegation-first prompt queries with this * parameter before deciding whether to delegate or self-execute. * * Format: canonical `:` strings * (`gmail:read`, `oauth:gmail`, `slack:post`, etc. — see * CapabilityInferenceService for the registry). */ capability?: string; } /** * Result of a recall operation */ export interface RecallResult { /** Memories from agent level */ agentMemories: string[]; /** Memories from project level */ projectMemories: string[]; /** Combined formatted result */ combined: string; /** Current project goals (if projectPath provided) */ activeGoals?: string[]; /** Current team focus (if projectPath provided) */ currentFocus?: string; /** Active tasks for this agent */ activeTasks?: Array<{ id: string; name: string; status: string; hasWorkingNotes: boolean; }>; /** * Task-history entries matching {@link RecallParams.capability}. Present * when the caller passed a capability filter. Sorted most-recent first; * empty array means "no team member has demonstrated this capability." */ taskHistory?: TaskHistoryEntry[]; } /** * Parameters for recording a learning */ export interface LearningParams { /** Agent identifier */ agentId: string; /** Agent's role */ agentRole: string; /** Project path */ projectPath: string; /** The learning content */ learning: string; /** Related task/ticket ID */ relatedTask?: string; /** Related file paths */ relatedFiles?: string[]; } /** * Interface for the unified Memory Service */ export interface IMemoryService { remember(params: RememberParams): Promise; recall(params: RecallParams): Promise; getFullContext(agentId: string, projectPath: string): Promise; recordLearning(params: LearningParams): Promise; initializeForSession(agentId: string, role: string, projectPath: string): Promise; getAgentMemoryService(): IAgentMemoryService; getProjectMemoryService(): IProjectMemoryService; } /** * Unified Memory Service Coordinator * * Provides a single entry point for all memory operations, coordinating * between agent-level and project-level memory services. * * @example * ```typescript * const memoryService = MemoryService.getInstance(); * * // Initialize for a session * await memoryService.initializeForSession('dev-001', 'developer', '/path/to/project'); * * // Remember something * await memoryService.remember({ * agentId: 'dev-001', * projectPath: '/path/to/project', * content: 'Always use async/await instead of callbacks', * category: 'pattern', * scope: 'project', * metadata: { title: 'Async Pattern' } * }); * * // Get full context for prompts * const context = await memoryService.getFullContext('dev-001', '/path/to/project'); * ``` */ export declare class MemoryService implements IMemoryService { private static instance; private readonly agentMemory; private readonly projectMemory; private readonly logger; /** * Creates a new MemoryService instance */ private constructor(); /** * Backwards-compatible no-op. Kept so existing call sites in `index.ts` * compile during the TaskTrackingService deletion window. Operational * context enrichment now reads from the V3 task-pool directly (see * {@link enrichWithOperational}). * * @deprecated Will be removed in a follow-up. Do not call from new code. */ setTaskTrackingService(_service: unknown): void; /** * Resolve the wiki vault paths that back semantic recall for a given scope. * * The retired embedding/vector store has been replaced by BM25 search over * the LLM-wiki vaults (see {@link wikiRecallSearch}). This maps a memory * scope to the vaults whose `llm-curated/` content should be searched: * - `project` → the project's own vault (`/.crewly/wiki`) * - `global` → the cross-project `~/.crewly/global-wiki` plus every team * vault (`~/.crewly/teams//wiki`); agent-scope learnings are mirrored * into these by {@link ingestToWiki}. * * Only vaults that actually exist (carry a `SCHEMA.md`) are returned. * * @param scope - Memory scope being recalled. * @param projectPath - Project root, required for `project` scope. * @returns Absolute vault paths to search (possibly empty). */ private resolveRecallVaults; /** * Gets the singleton instance of MemoryService * * @returns The singleton MemoryService instance */ static getInstance(): MemoryService; /** * Clears the singleton instance (useful for testing) */ static clearInstance(): void; /** * Gets the underlying AgentMemoryService * * @returns The AgentMemoryService instance */ getAgentMemoryService(): IAgentMemoryService; /** * Gets the underlying ProjectMemoryService * * @returns The ProjectMemoryService instance */ getProjectMemoryService(): IProjectMemoryService; /** * Maps RememberCategory to RoleKnowledgeCategory for agent memory. * * Public-API categories that map cleanly to internal storage: * - `fact` / `decision` → `best-practice` (positive learnings the agent should follow) * - `pattern` / `preference` → `workflow` (how-to / process knowledge) * - `gotcha` → `anti-pattern` (things to avoid; F4 fix 2026-05-06) * * Categories that do NOT reach this mapper because `rememberForAgent` * routes them differently or rejects them: `relationship` and * `user_preference` are project-only by design. */ private mapToKnowledgeCategory; /** * Derives a content-aware title when the caller omits `metadata.title`. * * Defense-in-depth for the P0-SEV silent-success fix: even after dedup is * tightened to require title+content match, two different rememberings that * BOTH lacked an explicit title used to collide on the same default * ('Gotcha', 'Untitled Pattern', 'Untitled Decision'). Auto-deriving from * content makes those titles distinct, so any future dedup-predicate * regression cannot fully silently collapse them. * * Strategy: take the first ~60 chars of content, prefer a word boundary; * fall back to the caller-provided default if content is empty. * * @param content - The user-supplied content payload * @param fallback - The category-specific default title to use when content is empty * @returns A derived title string (max ~60 chars), never empty */ private deriveTitleFromContent; /** * Parses a preference string into AgentPreferences */ private parsePreference; /** * Checks if a learning is role-relevant (should be stored in agent memory) */ private isRoleRelevant; /** * Filters memories by relevance to a context. * * **M3 (spec §183-187):** Default recall hides superseded and expired * entries via {@link isHiddenFromDefaultRecall}. They remain in the raw * store for audit / explicit recall, but they no longer pollute the * agent's recall path. * * **M4 (NOTE-A):** Pass `includeHidden=true` to surface superseded / * expired entries (audit-only paths). Production prompt-injection callers * must leave this `false` (default). */ private filterRelevant; /** * Formats search results into strings */ private formatSearchResults; /** * Combines agent and project memories into a formatted string */ /** * Combines agent and project memories into a formatted string. * * Separates completed task markers (#219) into their own section so * orchestrators and PMs can easily identify work that is already done * and avoid re-delegating it. * * @param result - The recall result containing agent and project memories * @returns Formatted combined string with clear section headings */ private combineMemories; /** * Mirror a remembered learning into the LLM-wiki so BM25 recall can surface * it. Replaces the retired embed-into-vector-store path: instead of computing * an embedding (the Gemini model was retired → 404), we append the content to * the target vault's `llm-curated/log.md` via {@link WikiIngestService}. * * No-ops silently when the target vault does not exist yet (no SCHEMA.md) so a * first-run machine without an initialised wiki still stores the file-based * memory without error. Fire-and-forget; never throws. * * @param id - Unique identifier for the memory entry (used as the source ref). * @param content - Text content to ingest. * @param metadata - Memory metadata (category/title surface in the log header). * @param scope - Storage scope ('global' → global-wiki, 'project' → project vault). * @param projectPath - Required when scope is 'project'. */ private ingestToWiki; /** * BM25 full-text search over the LLM-wiki vaults backing semantic recall. * Replaces the retired embedding/vector-store search: ranks `.md` pages by * Okapi BM25 (see {@link WikiSearchService}) — CJK-aware, no external index, * no API key. Falls back gracefully to empty results when no vault exists. * * @param context - Search query text. * @param scope - Storage scope ('global' or 'project'). * @param projectPath - Required when scope is 'project'. * @param limit - Maximum number of results. * @returns Formatted memory strings from the BM25 search. */ private wikiRecallSearch; /** * Merge BM25 wiki hits into a base list of memory strings, de-duplicating * and capping the result to `limit`. The base (file-based) memories keep * priority; wiki hits fill any remaining slots. * * @param base - File-based memories already collected for this scope. * @param extra - BM25 wiki hits to fold in. * @param limit - Maximum size of the merged list. * @returns The merged, de-duplicated, capped list. */ private mergeCapped; /** * Human-readable label for a wiki vault, derived from its path: * `global-wiki` → `global`, `teams//wiki` → `team:`, * `/.crewly/wiki` → the project directory name. * * @param vaultPath - Absolute vault path. * @returns A short label used to prefix recall hits. */ /** * The vault that records an unanswered recall: the global vault when it * was searched (a global recall), else the single project vault. Team * vaults never own a recall miss — they are only asked directly through * wiki-query. * * @param vaultPaths - Vaults the recall searched * @returns Vault root, or null when none qualifies */ private recallMissOwner; private wikiVaultLabel; /** * Unified remember operation - stores content in appropriate memory * * @param params - Remember parameters * @returns ID of the stored memory entry * * @example * ```typescript * // Remember a pattern for the project * await memoryService.remember({ * agentId: 'dev-001', * projectPath: '/path/to/project', * content: 'Use error wrapper for all API endpoints', * category: 'pattern', * scope: 'project', * metadata: { * title: 'API Error Handling', * patternCategory: 'api', * example: 'handleApiError(handler)' * } * }); * ``` */ remember(params: RememberParams): Promise; /** * Stores memory at agent level. * * Valid public-API categories for agent scope: * - `fact`, `pattern` → role knowledge (existing behavior) * - `gotcha` → role knowledge with internal `anti-pattern` category * (F4 fix 2026-05-06: agents can now record personal gotchas without * polluting project memory). * - `preference` → updates AgentPreferences struct * * Categories `relationship` and `user_preference` remain project-only * by design (relationship describes codebase component edges; * user_preference is scoped to a specific project's user). */ private rememberForAgent; /** * Stores memory at project level */ private rememberForProject; /** * Unified recall operation - retrieves relevant memories * * @param params - Recall parameters * @returns Recall results from appropriate scopes * * @example * ```typescript * const memories = await memoryService.recall({ * agentId: 'dev-001', * projectPath: '/path/to/project', * context: 'error handling in API endpoints', * scope: 'both', * limit: 10 * }); * console.log(memories.combined); * ``` */ recall(params: RecallParams): Promise; /** * Enrich recall result with operational context: goals, focus, active tasks. * All lookups are non-fatal — missing data is silently skipped. */ private enrichWithOperationalContext; /** * Generates combined context from both agent and project memory * * @param agentId - Agent identifier * @param projectPath - Project path * @returns Combined context string for prompt injection */ getFullContext(agentId: string, projectPath: string): Promise; /** * Records a learning to both project learnings and potentially agent memory * * @param params - Learning parameters */ recordLearning(params: LearningParams): Promise; /** * Initializes memory for a new session * * @param agentId - Agent identifier * @param role - Agent's role * @param projectPath - Project path */ initializeForSession(agentId: string, role: string, projectPath: string): Promise; /** * Recalls knowledge from all agents that have worked on a project * * Reads the project's agents-index.json, then searches each agent's role * knowledge for entries relevant to the given context. Results are merged, * deduplicated, and sorted by relevance. * * @param projectPath - Absolute path to the project * @param context - Search context for relevance filtering * @param limit - Maximum number of results (default 20) * @returns Array of formatted memory strings from all agents * * @example * ```typescript * const teamKnowledge = await memoryService.recallFromAllAgents( * '/projects/app', * 'authentication middleware', * 15, * ); * ``` */ recallFromAllAgents(projectPath: string, context: string, limit?: number): Promise; } /** * Normalize a `supersedes` value (string, comma-separated string or array) * into a list of non-empty ids. * * @param value - Raw value * @returns Ids, possibly empty */ export declare function normalizeIdList(value: string | string[] | undefined): string[]; //# sourceMappingURL=memory.service.d.ts.map