memorix 1.2.4 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -184,6 +184,8 @@ interface TimelineContext {
184
184
  before: IndexEntry[];
185
185
  after: IndexEntry[];
186
186
  }
187
+ /** Retrieval work allowed for a search request. */
188
+ type RetrievalQuality = 'fast' | 'balanced' | 'thorough';
187
189
  /** Search options for the compact engine */
188
190
  interface SearchOptions {
189
191
  query: string;
@@ -202,6 +204,11 @@ interface SearchOptions {
202
204
  trackAccess?: boolean;
203
205
  /** Internal reader context for agent-facing visibility filtering. */
204
206
  reader?: ObservationReader;
207
+ /**
208
+ * `fast` stays fully local, `balanced` (default) permits embeddings, and
209
+ * `thorough` explicitly permits optional LLM query rewrite and reranking.
210
+ */
211
+ quality?: RetrievalQuality;
205
212
  }
206
213
  /** Topic key family heuristics for suggesting stable topic keys */
207
214
  declare const TOPIC_KEY_FAMILIES: Record<string, string[]>;
@@ -446,4 +453,4 @@ interface MCPConfigAdapter {
446
453
  getConfigPath(projectRoot?: string): string;
447
454
  }
448
455
 
449
- export { type AgentTarget, DEFAULT_CONFIG, type DetectionFailure, type DetectionFailureReason, type DetectionResult, type DocumentType, type Entity, type IndexEntry, type KnowledgeGraph, type KnowledgeLayer, type MCPConfigAdapter, type MCPServerEntry, type MemorixConfig, type MemorixDocument, type MemoryRef, type MiniSkill, OBSERVATION_ICONS, type Observation, type ObservationAdmissionState, type ObservationReader, type ObservationRef, type ObservationStatus, type ObservationType, type ObservationVisibility, type ProgressInfo, type ProjectInfo, type Relation, type RuleFormatAdapter, type RuleSource, type SearchOptions, type Session, type SkillConflict, type SkillEntry, type SnapshotObservation, type SourceSnapshot, TOPIC_KEY_FAMILIES, type TimelineContext, type UnifiedRule, type WorkflowEntry, type WorkspaceSyncResult };
456
+ export { type AgentTarget, DEFAULT_CONFIG, type DetectionFailure, type DetectionFailureReason, type DetectionResult, type DocumentType, type Entity, type IndexEntry, type KnowledgeGraph, type KnowledgeLayer, type MCPConfigAdapter, type MCPServerEntry, type MemorixConfig, type MemorixDocument, type MemoryRef, type MiniSkill, OBSERVATION_ICONS, type Observation, type ObservationAdmissionState, type ObservationReader, type ObservationRef, type ObservationStatus, type ObservationType, type ObservationVisibility, type ProgressInfo, type ProjectInfo, type Relation, type RetrievalQuality, type RuleFormatAdapter, type RuleSource, type SearchOptions, type Session, type SkillConflict, type SkillEntry, type SnapshotObservation, type SourceSnapshot, TOPIC_KEY_FAMILIES, type TimelineContext, type UnifiedRule, type WorkflowEntry, type WorkspaceSyncResult };
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Memorix Core Types\n *\n * Data model sources:\n * - Entity/Relation/KnowledgeGraph: MCP Official Memory Server (v0.6.3)\n * - Observation/ObservationType: claude-mem Progressive Disclosure\n * - UnifiedRule/RuleSource: Memorix original (rules sync)\n *\n * Designed for extensibility: new agent formats (Kiro, Copilot, Antigravity)\n * can be added by extending RuleSource and adding format adapters.\n */\n\n// ============================================================\n// Knowledge Graph (adopted from MCP Official Memory Server)\n// ============================================================\n\n/** A node in the knowledge graph representing a concept, component, or config */\nexport interface Entity {\n name: string;\n entityType: string;\n observations: string[];\n}\n\n/** A directed edge between two entities */\nexport interface Relation {\n from: string;\n to: string;\n relationType: string;\n}\n\n/** The complete knowledge graph */\nexport interface KnowledgeGraph {\n entities: Entity[];\n relations: Relation[];\n}\n\n// ============================================================\n// Observation (adopted from claude-mem Progressive Disclosure)\n// ============================================================\n\n/**\n * Observation type classification using claude-mem's icon-based legend system.\n *\n * Icon mapping:\n * [SESSION] session-request — User's original goal\n * [GOTCHA] gotcha — Critical pitfall / trap\n * [FIX] problem-solution — Bug fix or workaround\n * [INFO] how-it-works — Technical explanation\n * [CHANGE] what-changed — Code/architecture change\n * [DISCOVERY] discovery — New learning or insight\n * [WHY] why-it-exists — Design rationale\n * [DECISION] decision — Architecture decision\n * [TRADEOFF] trade-off — Deliberate compromise\n * [REASONING] reasoning — Why this approach was chosen (System 2 reasoning trace)\n * [PROBE] probe — Operational heartbeat / connectivity check (short-lived, excluded from default search)\n */\nexport type ObservationType =\n | 'session-request'\n | 'gotcha'\n | 'problem-solution'\n | 'how-it-works'\n | 'what-changed'\n | 'discovery'\n | 'why-it-exists'\n | 'decision'\n | 'trade-off'\n | 'reasoning'\n | 'probe';\n\n/** Map from ObservationType to display icon */\nexport const OBSERVATION_ICONS: Record<ObservationType, string> = {\n 'session-request': '[SESSION]',\n 'gotcha': '[GOTCHA]',\n 'problem-solution': '[FIX]',\n 'how-it-works': '[INFO]',\n 'what-changed': '[CHANGE]',\n 'discovery': '[DISCOVERY]',\n 'why-it-exists': '[WHY]',\n 'decision': '[DECISION]',\n 'trade-off': '[TRADEOFF]',\n 'reasoning': '[REASONING]',\n 'probe': '[PROBE]',\n};\n\n/** Observation lifecycle status */\nexport type ObservationStatus = 'active' | 'resolved' | 'archived';\n\n/**\n * Control-plane admission state for automatically captured observations.\n * Candidate and ephemeral records remain inspectable, but only qualified\n * records are eligible for automatic context delivery. Missing state preserves\n * legacy behavior for observations written before this policy existed.\n */\nexport type ObservationAdmissionState = 'ephemeral' | 'candidate' | 'qualified';\n\n/**\n * Who may retrieve an observation through agent-facing memory surfaces.\n * Missing visibility is a legacy record and resolves to `project`.\n */\nexport type ObservationVisibility = 'personal' | 'project' | 'team';\n\n/**\n * Identity available to an agent-facing retrieval call. Internal maintenance\n * deliberately omits this and may inspect all project records for lifecycle work.\n */\nexport interface ObservationReader {\n /** Bound project for a normal project-scoped call. Omit only for explicit global search. */\n projectId?: string;\n /** Stable coordination identity, available after an explicit team join. */\n agentId?: string;\n /** True only when agentId is an active member of the bound project team. */\n isTeamMember?: boolean;\n}\n\n/** Progress tracking for task/feature observations */\nexport interface ProgressInfo {\n feature: string;\n status: 'in-progress' | 'completed' | 'blocked';\n completion?: number;\n}\n\n/** A rich observation record attached to an entity */\nexport interface Observation {\n id: number;\n entityName: string;\n type: ObservationType;\n title: string;\n narrative: string;\n facts: string[];\n filesModified: string[];\n concepts: string[];\n tokens: number;\n createdAt: string;\n updatedAt?: string;\n projectId: string;\n /** Whether the observation contains causal language (because, due to, etc.) */\n hasCausalLanguage?: boolean;\n /** Optional topic key for upsert — same project+topicKey updates existing observation */\n topicKey?: string;\n /** How many times this observation was revised via topic key upsert (starts at 1) */\n revisionCount?: number;\n /** Session ID this observation belongs to */\n sessionId?: string;\n /** Lifecycle status: active (default) → resolved → archived */\n status?: ObservationStatus;\n /** ID of the observation that superseded this one (set when auto-resolved by topicKey upsert) */\n supersededBy?: number;\n /** Progress tracking for task/feature observations */\n progress?: ProgressInfo;\n /** Origin of this observation: agent (IDE hooks/MCP), git (commit ingest), manual (CLI) */\n source?: 'agent' | 'git' | 'manual';\n /** Git commit hash if source is 'git' */\n commitHash?: string;\n /** Related commit hashes — links reasoning memories to the commits they explain */\n relatedCommits?: string[];\n /** Related entity names — explicit cross-references to other memory entities */\n relatedEntities?: string[];\n /** Provenance detail: how this observation entered the system */\n sourceDetail?: 'explicit' | 'hook' | 'git-ingest';\n /** Value category from formation pipeline evaluation */\n valueCategory?: 'core' | 'contextual' | 'ephemeral';\n /** Control-plane admission state for automatic capture and delivery. */\n admissionState?: ObservationAdmissionState;\n /** Short, sanitized explanation for the latest admission decision. */\n admissionReason?: string;\n /** Retrieval scope. Legacy records without a value remain project-visible. */\n visibility?: ObservationVisibility;\n /** Explicit additional readers for a personal record, used by targeted handoffs. */\n sharedWithAgentIds?: string[];\n /** Phase 4a: Agent ID that created this observation (team attribution) */\n createdByAgentId?: string;\n /** Phase 4a: Monotonic write generation — snapshot of storage_generation at write time (watermark coherence) */\n writeGeneration?: number;\n}\n\n// ============================================================\n// Session Lifecycle (inspired by Engram's session management)\n// ============================================================\n\n/** A coding session tracked by Memorix */\nexport interface Session {\n id: string;\n projectId: string;\n startedAt: string;\n endedAt?: string;\n summary?: string;\n status: 'active' | 'completed';\n /** Agent/IDE that started this session */\n agent?: string;\n}\n\n// ============================================================\n// Compact Engine (adopted from claude-mem 3-layer workflow)\n// ============================================================\n\n/** L1 index entry — lightweight, ~50-100 tokens per result */\nexport interface IndexEntry {\n id: number;\n time: string;\n type: ObservationType;\n icon: string;\n title: string;\n tokens: number;\n /** Relevance score from search (time-decayed). Used by compact engine. */\n score?: number;\n /** Project that owns this observation. Needed to disambiguate global results. */\n projectId?: string;\n /** Origin of the memory for source-aware retrieval and display. */\n source?: 'agent' | 'git' | 'manual';\n /** Provenance detail for source-aware display */\n sourceDetail?: 'explicit' | 'hook' | 'git-ingest';\n /** Value category for source-aware ranking */\n valueCategory?: 'core' | 'contextual' | 'ephemeral';\n /** Control-plane admission state when known. */\n admissionState?: ObservationAdmissionState;\n /** Retrieval scope when known. */\n visibility?: ObservationVisibility;\n /** Explainable recall: why this result matched. */\n matchedFields?: string[];\n /** Entity name — used for entity-affinity scoring and workstream deduplication. */\n entityName?: string;\n /** Document type: observation or mini-skill (Phase 3a) */\n documentType?: DocumentType;\n /** Knowledge layer for layer-aware ranking (Phase 3a) */\n knowledgeLayer?: KnowledgeLayer;\n}\n\n/** Explicit reference to an observation, optionally scoped to a project. */\nexport interface ObservationRef {\n id: number;\n projectId?: string;\n}\n\n/** L2 timeline context — observations around an anchor */\nexport interface TimelineContext {\n anchorId: number;\n anchorEntry: IndexEntry | null;\n before: IndexEntry[];\n after: IndexEntry[];\n}\n\n/** Search options for the compact engine */\nexport interface SearchOptions {\n query: string;\n limit?: number;\n type?: ObservationType;\n projectId?: string;\n since?: string;\n until?: string;\n /** Token budget — trim results to fit within this many tokens (0 = unlimited) */\n maxTokens?: number;\n /** Filter by observation status. Default: 'active' (only show active memories) */\n status?: ObservationStatus | 'all';\n /** Filter by observation source: 'agent', 'git', 'manual', or undefined for all */\n source?: 'agent' | 'git' | 'manual';\n /** Internal observability probes can disable access tracking without affecting normal search behavior. */\n trackAccess?: boolean;\n /** Internal reader context for agent-facing visibility filtering. */\n reader?: ObservationReader;\n}\n\n/** Topic key family heuristics for suggesting stable topic keys */\nexport const TOPIC_KEY_FAMILIES: Record<string, string[]> = {\n 'architecture': ['architecture', 'design', 'adr', 'structure', 'pattern'],\n 'bug': ['bugfix', 'fix', 'error', 'regression', 'crash', 'problem-solution'],\n 'decision': ['decision', 'trade-off', 'choice', 'strategy'],\n 'config': ['config', 'setup', 'env', 'environment', 'deployment'],\n 'discovery': ['discovery', 'learning', 'insight', 'gotcha'],\n 'pattern': ['pattern', 'convention', 'standard', 'best-practice'],\n};\n\n// ============================================================\n// Orama Document Schema\n// ============================================================\n\n/** The document shape stored in Orama */\nexport interface MemorixDocument {\n id: string;\n observationId: number;\n entityName: string;\n type: string;\n title: string;\n narrative: string;\n facts: string;\n filesModified: string;\n concepts: string;\n tokens: number;\n createdAt: string;\n projectId: string;\n /** Number of times this observation was returned in search results */\n accessCount: number;\n /** ISO timestamp of last access via search/detail */\n lastAccessedAt: string;\n /** Lifecycle status: active, resolved, archived */\n status: string;\n /** Origin: agent, git, manual */\n source: string;\n /** Provenance detail: explicit, hook, or git-ingest */\n sourceDetail?: string;\n /** Value category from formation evaluation */\n valueCategory?: string;\n /** Control-plane admission state for automatic capture and delivery. */\n admissionState?: string;\n /** Short, sanitized explanation for the latest admission decision. */\n admissionReason?: string;\n /** Retrieval scope for agent-facing filtering. */\n visibility?: string;\n /** Agent that owns a personal record, or authored a shared record. */\n createdByAgentId?: string;\n /** Explicit readers for a targeted personal record. */\n sharedWithAgentIds?: string;\n /** Optional vector embedding for semantic/hybrid retrieval */\n embedding?: number[];\n /** Document type: observation or mini-skill (Phase 3a) */\n documentType?: DocumentType;\n /** Knowledge layer for layer-aware ranking (Phase 3a) */\n knowledgeLayer?: KnowledgeLayer;\n}\n\n// ============================================================\n// Rules System (Memorix original — extensible for new agents)\n// ============================================================\n\n/**\n * Supported agent/IDE rule sources.\n * All 7 major AI IDEs are supported.\n */\nexport type RuleSource =\n | 'cursor'\n | 'claude-code'\n | 'codex'\n | 'windsurf'\n | 'antigravity'\n | 'gemini-cli'\n | 'copilot'\n | 'kiro'\n | 'trae'\n | 'memorix';\n\n/** A parsed rule in the unified intermediate representation */\nexport interface UnifiedRule {\n id: string;\n content: string;\n description?: string;\n source: RuleSource;\n scope: 'global' | 'project' | 'path-specific';\n paths?: string[];\n alwaysApply?: boolean;\n priority: number;\n hash: string;\n}\n\n/**\n * Format adapter interface — implement this for each agent/IDE.\n * Adding a new agent (e.g., Kiro) only requires implementing this interface.\n */\nexport interface RuleFormatAdapter {\n /** Unique identifier for this agent format */\n readonly source: RuleSource;\n\n /** File paths/globs this adapter can parse */\n readonly filePatterns: string[];\n\n /** Parse rule files into unified representation */\n parse(filePath: string, content: string): UnifiedRule[];\n\n /** Generate rule file content from unified representation */\n generate(rules: UnifiedRule[]): { filePath: string; content: string }[];\n}\n\n// ============================================================\n// Project Identity\n// ============================================================\n\nexport interface ProjectInfo {\n id: string;\n name: string;\n gitRemote?: string;\n rootPath: string;\n}\n\n/**\n * Diagnostic failure info from project detection.\n * Tells callers exactly WHY detection failed so they can report actionable errors.\n */\nexport type DetectionFailureReason =\n | 'path_not_found'\n | 'not_a_directory'\n | 'no_git'\n | 'git_worktree_error'\n | 'git_safe_directory'\n | 'remote_resolve_failed';\n\nexport interface DetectionFailure {\n reason: DetectionFailureReason;\n path: string;\n detail: string;\n}\n\nexport interface DetectionResult {\n project: ProjectInfo | null;\n failure: DetectionFailure | null;\n}\n\n// ============================================================\n// Memorix Server Configuration\n// ============================================================\n\nexport interface MemorixConfig {\n dataDir: string;\n projectId: string;\n projectName: string;\n enableEmbeddings: boolean;\n enableRulesSync: boolean;\n watchRuleFiles: boolean;\n}\n\nexport const DEFAULT_CONFIG: Partial<MemorixConfig> = {\n enableEmbeddings: false,\n enableRulesSync: false,\n watchRuleFiles: false,\n};\n\n// ============================================================\n// Workspace Sync — Cross-Agent workspace migration\n// ============================================================\n\n/** Supported agent targets for workspace sync */\nexport type AgentTarget =\n | 'windsurf'\n | 'cursor'\n | 'claude-code'\n | 'codex'\n | 'copilot'\n | 'antigravity'\n | 'gemini-cli'\n | 'openclaw'\n | 'hermes'\n | 'omp'\n | 'kiro'\n | 'opencode'\n | 'trae';\n\n/** A unified MCP server entry across all agent config formats */\nexport interface MCPServerEntry {\n name: string;\n /** Command for stdio transport */\n command: string;\n /** Args for stdio transport */\n args: string[];\n /** Environment variables */\n env?: Record<string, string> | null;\n /** URL for HTTP/SSE transport (Codex uses `url`, Windsurf uses `serverUrl`) */\n url?: string;\n /** HTTP headers (Windsurf uses `headers` for HTTP transport) */\n headers?: Record<string, string>;\n /** Whether this server is disabled */\n disabled?: boolean;\n /** Claude Code: force small core tool sets to load before the first prompt */\n alwaysLoad?: boolean;\n}\n\n/** Unified workflow entry */\nexport interface WorkflowEntry {\n name: string;\n description: string;\n content: string;\n source: AgentTarget;\n filePath: string;\n}\n\n/** A skill folder discovered from an agent's skills directory */\nexport interface SkillEntry {\n name: string;\n description: string;\n sourcePath: string;\n sourceAgent: AgentTarget;\n}\n\n/** Conflict when two agents have a skill with the same folder name */\nexport interface SkillConflict {\n name: string;\n kept: SkillEntry;\n skipped: SkillEntry;\n}\n\n/** Result of a workspace sync operation */\nexport interface WorkspaceSyncResult {\n mcpServers: {\n scanned: MCPServerEntry[];\n generated: { filePath: string; content: string }[];\n };\n workflows: {\n scanned: WorkflowEntry[];\n generated: { filePath: string; content: string }[];\n };\n rules: {\n scanned: number;\n generated: number;\n };\n skills: {\n scanned: SkillEntry[];\n conflicts: SkillConflict[];\n copied: string[];\n skipped: string[];\n };\n}\n\n// ============================================================\n// Mini-Skills — Promoted memories that never decay\n// ============================================================\n\n/** A mini-skill promoted from one or more observations */\nexport interface MiniSkill {\n id: number;\n /** Observation IDs this mini-skill was derived from (live refs, best-effort) */\n sourceObservationIds: number[];\n /** Entity the source observations belong to */\n sourceEntity: string;\n /** Short title for the skill */\n title: string;\n /** What the agent should do (imperative instruction) */\n instruction: string;\n /** When this skill should be applied (scenario description) */\n trigger: string;\n /** Key facts extracted from source observations */\n facts: string[];\n /** Project this skill belongs to */\n projectId: string;\n /** ISO timestamp */\n createdAt: string;\n /** How many times this skill was injected in session_start */\n usedCount: number;\n /** Classification tags */\n tags: string[];\n /** Frozen source observation content at promote time (JSON, immutable provenance proof) */\n sourceSnapshot?: string;\n /** ISO timestamp of last modification (Phase 3a: set once at creation) */\n updatedAt?: string;\n}\n\n// ============================================================\n// Source Snapshot — immutable provenance proof for promoted knowledge\n// ============================================================\n\n/** A single observation entry within a source snapshot */\nexport interface SnapshotObservation {\n id: number;\n title: string;\n type: string;\n narrative: string;\n facts: string[];\n entityName: string;\n projectId: string;\n createdAt: string;\n /** Frozen source detail for provenance (explicit / hook / git-ingest) */\n sourceDetail?: string;\n}\n\n/** Frozen source content captured at promote time */\nexport interface SourceSnapshot {\n observations: SnapshotObservation[];\n promotedAt: string;\n}\n\n// ============================================================\n// Knowledge Layer — Phase 3a retrieval classification\n// ============================================================\n\n/** Classification of knowledge for layer-aware ranking */\nexport type KnowledgeLayer = 'project-truth' | 'promoted' | 'evidence';\n\n/** Document type discriminator for Orama index */\nexport type DocumentType = 'observation' | 'mini-skill';\n\n// ============================================================\n// Typed Memory Reference — Phase 3a reference protocol\n// ============================================================\n\n/** A typed reference to a memory object (observation or mini-skill) */\nexport interface MemoryRef {\n kind: 'obs' | 'skill';\n id: number;\n projectId?: string;\n}\n\n/** MCP config format adapter interface */\nexport interface MCPConfigAdapter {\n readonly source: AgentTarget;\n /** Parse MCP server entries from a config file */\n parse(content: string): MCPServerEntry[];\n /** Generate config file content from MCP server entries */\n generate(servers: MCPServerEntry[]): string;\n /** Get the default config file path for this agent */\n getConfigPath(projectRoot?: string): string;\n}\n"],"mappings":";AAsEO,IAAM,oBAAqD;AAAA,EAChE,mBAAmB;AAAA,EACnB,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS;AACX;AAoLO,IAAM,qBAA+C;AAAA,EAC1D,gBAAgB,CAAC,gBAAgB,UAAU,OAAO,aAAa,SAAS;AAAA,EACxE,OAAO,CAAC,UAAU,OAAO,SAAS,cAAc,SAAS,kBAAkB;AAAA,EAC3E,YAAY,CAAC,YAAY,aAAa,UAAU,UAAU;AAAA,EAC1D,UAAU,CAAC,UAAU,SAAS,OAAO,eAAe,YAAY;AAAA,EAChE,aAAa,CAAC,aAAa,YAAY,WAAW,QAAQ;AAAA,EAC1D,WAAW,CAAC,WAAW,cAAc,YAAY,eAAe;AAClE;AAoJO,IAAM,iBAAyC;AAAA,EACpD,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,gBAAgB;AAClB;","names":[]}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Memorix Core Types\n *\n * Data model sources:\n * - Entity/Relation/KnowledgeGraph: MCP Official Memory Server (v0.6.3)\n * - Observation/ObservationType: claude-mem Progressive Disclosure\n * - UnifiedRule/RuleSource: Memorix original (rules sync)\n *\n * Designed for extensibility: new agent formats (Kiro, Copilot, Antigravity)\n * can be added by extending RuleSource and adding format adapters.\n */\n\n// ============================================================\n// Knowledge Graph (adopted from MCP Official Memory Server)\n// ============================================================\n\n/** A node in the knowledge graph representing a concept, component, or config */\nexport interface Entity {\n name: string;\n entityType: string;\n observations: string[];\n}\n\n/** A directed edge between two entities */\nexport interface Relation {\n from: string;\n to: string;\n relationType: string;\n}\n\n/** The complete knowledge graph */\nexport interface KnowledgeGraph {\n entities: Entity[];\n relations: Relation[];\n}\n\n// ============================================================\n// Observation (adopted from claude-mem Progressive Disclosure)\n// ============================================================\n\n/**\n * Observation type classification using claude-mem's icon-based legend system.\n *\n * Icon mapping:\n * [SESSION] session-request — User's original goal\n * [GOTCHA] gotcha — Critical pitfall / trap\n * [FIX] problem-solution — Bug fix or workaround\n * [INFO] how-it-works — Technical explanation\n * [CHANGE] what-changed — Code/architecture change\n * [DISCOVERY] discovery — New learning or insight\n * [WHY] why-it-exists — Design rationale\n * [DECISION] decision — Architecture decision\n * [TRADEOFF] trade-off — Deliberate compromise\n * [REASONING] reasoning — Why this approach was chosen (System 2 reasoning trace)\n * [PROBE] probe — Operational heartbeat / connectivity check (short-lived, excluded from default search)\n */\nexport type ObservationType =\n | 'session-request'\n | 'gotcha'\n | 'problem-solution'\n | 'how-it-works'\n | 'what-changed'\n | 'discovery'\n | 'why-it-exists'\n | 'decision'\n | 'trade-off'\n | 'reasoning'\n | 'probe';\n\n/** Map from ObservationType to display icon */\nexport const OBSERVATION_ICONS: Record<ObservationType, string> = {\n 'session-request': '[SESSION]',\n 'gotcha': '[GOTCHA]',\n 'problem-solution': '[FIX]',\n 'how-it-works': '[INFO]',\n 'what-changed': '[CHANGE]',\n 'discovery': '[DISCOVERY]',\n 'why-it-exists': '[WHY]',\n 'decision': '[DECISION]',\n 'trade-off': '[TRADEOFF]',\n 'reasoning': '[REASONING]',\n 'probe': '[PROBE]',\n};\n\n/** Observation lifecycle status */\nexport type ObservationStatus = 'active' | 'resolved' | 'archived';\n\n/**\n * Control-plane admission state for automatically captured observations.\n * Candidate and ephemeral records remain inspectable, but only qualified\n * records are eligible for automatic context delivery. Missing state preserves\n * legacy behavior for observations written before this policy existed.\n */\nexport type ObservationAdmissionState = 'ephemeral' | 'candidate' | 'qualified';\n\n/**\n * Who may retrieve an observation through agent-facing memory surfaces.\n * Missing visibility is a legacy record and resolves to `project`.\n */\nexport type ObservationVisibility = 'personal' | 'project' | 'team';\n\n/**\n * Identity available to an agent-facing retrieval call. Internal maintenance\n * deliberately omits this and may inspect all project records for lifecycle work.\n */\nexport interface ObservationReader {\n /** Bound project for a normal project-scoped call. Omit only for explicit global search. */\n projectId?: string;\n /** Stable coordination identity, available after an explicit team join. */\n agentId?: string;\n /** True only when agentId is an active member of the bound project team. */\n isTeamMember?: boolean;\n}\n\n/** Progress tracking for task/feature observations */\nexport interface ProgressInfo {\n feature: string;\n status: 'in-progress' | 'completed' | 'blocked';\n completion?: number;\n}\n\n/** A rich observation record attached to an entity */\nexport interface Observation {\n id: number;\n entityName: string;\n type: ObservationType;\n title: string;\n narrative: string;\n facts: string[];\n filesModified: string[];\n concepts: string[];\n tokens: number;\n createdAt: string;\n updatedAt?: string;\n projectId: string;\n /** Whether the observation contains causal language (because, due to, etc.) */\n hasCausalLanguage?: boolean;\n /** Optional topic key for upsert — same project+topicKey updates existing observation */\n topicKey?: string;\n /** How many times this observation was revised via topic key upsert (starts at 1) */\n revisionCount?: number;\n /** Session ID this observation belongs to */\n sessionId?: string;\n /** Lifecycle status: active (default) → resolved → archived */\n status?: ObservationStatus;\n /** ID of the observation that superseded this one (set when auto-resolved by topicKey upsert) */\n supersededBy?: number;\n /** Progress tracking for task/feature observations */\n progress?: ProgressInfo;\n /** Origin of this observation: agent (IDE hooks/MCP), git (commit ingest), manual (CLI) */\n source?: 'agent' | 'git' | 'manual';\n /** Git commit hash if source is 'git' */\n commitHash?: string;\n /** Related commit hashes — links reasoning memories to the commits they explain */\n relatedCommits?: string[];\n /** Related entity names — explicit cross-references to other memory entities */\n relatedEntities?: string[];\n /** Provenance detail: how this observation entered the system */\n sourceDetail?: 'explicit' | 'hook' | 'git-ingest';\n /** Value category from formation pipeline evaluation */\n valueCategory?: 'core' | 'contextual' | 'ephemeral';\n /** Control-plane admission state for automatic capture and delivery. */\n admissionState?: ObservationAdmissionState;\n /** Short, sanitized explanation for the latest admission decision. */\n admissionReason?: string;\n /** Retrieval scope. Legacy records without a value remain project-visible. */\n visibility?: ObservationVisibility;\n /** Explicit additional readers for a personal record, used by targeted handoffs. */\n sharedWithAgentIds?: string[];\n /** Phase 4a: Agent ID that created this observation (team attribution) */\n createdByAgentId?: string;\n /** Phase 4a: Monotonic write generation — snapshot of storage_generation at write time (watermark coherence) */\n writeGeneration?: number;\n}\n\n// ============================================================\n// Session Lifecycle (inspired by Engram's session management)\n// ============================================================\n\n/** A coding session tracked by Memorix */\nexport interface Session {\n id: string;\n projectId: string;\n startedAt: string;\n endedAt?: string;\n summary?: string;\n status: 'active' | 'completed';\n /** Agent/IDE that started this session */\n agent?: string;\n}\n\n// ============================================================\n// Compact Engine (adopted from claude-mem 3-layer workflow)\n// ============================================================\n\n/** L1 index entry — lightweight, ~50-100 tokens per result */\nexport interface IndexEntry {\n id: number;\n time: string;\n type: ObservationType;\n icon: string;\n title: string;\n tokens: number;\n /** Relevance score from search (time-decayed). Used by compact engine. */\n score?: number;\n /** Project that owns this observation. Needed to disambiguate global results. */\n projectId?: string;\n /** Origin of the memory for source-aware retrieval and display. */\n source?: 'agent' | 'git' | 'manual';\n /** Provenance detail for source-aware display */\n sourceDetail?: 'explicit' | 'hook' | 'git-ingest';\n /** Value category for source-aware ranking */\n valueCategory?: 'core' | 'contextual' | 'ephemeral';\n /** Control-plane admission state when known. */\n admissionState?: ObservationAdmissionState;\n /** Retrieval scope when known. */\n visibility?: ObservationVisibility;\n /** Explainable recall: why this result matched. */\n matchedFields?: string[];\n /** Entity name — used for entity-affinity scoring and workstream deduplication. */\n entityName?: string;\n /** Document type: observation or mini-skill (Phase 3a) */\n documentType?: DocumentType;\n /** Knowledge layer for layer-aware ranking (Phase 3a) */\n knowledgeLayer?: KnowledgeLayer;\n}\n\n/** Explicit reference to an observation, optionally scoped to a project. */\nexport interface ObservationRef {\n id: number;\n projectId?: string;\n}\n\n/** L2 timeline context — observations around an anchor */\nexport interface TimelineContext {\n anchorId: number;\n anchorEntry: IndexEntry | null;\n before: IndexEntry[];\n after: IndexEntry[];\n}\n\n/** Retrieval work allowed for a search request. */\nexport type RetrievalQuality = 'fast' | 'balanced' | 'thorough';\n\n/** Search options for the compact engine */\nexport interface SearchOptions {\n query: string;\n limit?: number;\n type?: ObservationType;\n projectId?: string;\n since?: string;\n until?: string;\n /** Token budget — trim results to fit within this many tokens (0 = unlimited) */\n maxTokens?: number;\n /** Filter by observation status. Default: 'active' (only show active memories) */\n status?: ObservationStatus | 'all';\n /** Filter by observation source: 'agent', 'git', 'manual', or undefined for all */\n source?: 'agent' | 'git' | 'manual';\n /** Internal observability probes can disable access tracking without affecting normal search behavior. */\n trackAccess?: boolean;\n /** Internal reader context for agent-facing visibility filtering. */\n reader?: ObservationReader;\n /**\n * `fast` stays fully local, `balanced` (default) permits embeddings, and\n * `thorough` explicitly permits optional LLM query rewrite and reranking.\n */\n quality?: RetrievalQuality;\n}\n\n/** Topic key family heuristics for suggesting stable topic keys */\nexport const TOPIC_KEY_FAMILIES: Record<string, string[]> = {\n 'architecture': ['architecture', 'design', 'adr', 'structure', 'pattern'],\n 'bug': ['bugfix', 'fix', 'error', 'regression', 'crash', 'problem-solution'],\n 'decision': ['decision', 'trade-off', 'choice', 'strategy'],\n 'config': ['config', 'setup', 'env', 'environment', 'deployment'],\n 'discovery': ['discovery', 'learning', 'insight', 'gotcha'],\n 'pattern': ['pattern', 'convention', 'standard', 'best-practice'],\n};\n\n// ============================================================\n// Orama Document Schema\n// ============================================================\n\n/** The document shape stored in Orama */\nexport interface MemorixDocument {\n id: string;\n observationId: number;\n entityName: string;\n type: string;\n title: string;\n narrative: string;\n facts: string;\n filesModified: string;\n concepts: string;\n tokens: number;\n createdAt: string;\n projectId: string;\n /** Number of times this observation was returned in search results */\n accessCount: number;\n /** ISO timestamp of last access via search/detail */\n lastAccessedAt: string;\n /** Lifecycle status: active, resolved, archived */\n status: string;\n /** Origin: agent, git, manual */\n source: string;\n /** Provenance detail: explicit, hook, or git-ingest */\n sourceDetail?: string;\n /** Value category from formation evaluation */\n valueCategory?: string;\n /** Control-plane admission state for automatic capture and delivery. */\n admissionState?: string;\n /** Short, sanitized explanation for the latest admission decision. */\n admissionReason?: string;\n /** Retrieval scope for agent-facing filtering. */\n visibility?: string;\n /** Agent that owns a personal record, or authored a shared record. */\n createdByAgentId?: string;\n /** Explicit readers for a targeted personal record. */\n sharedWithAgentIds?: string;\n /** Optional vector embedding for semantic/hybrid retrieval */\n embedding?: number[];\n /** Document type: observation or mini-skill (Phase 3a) */\n documentType?: DocumentType;\n /** Knowledge layer for layer-aware ranking (Phase 3a) */\n knowledgeLayer?: KnowledgeLayer;\n}\n\n// ============================================================\n// Rules System (Memorix original — extensible for new agents)\n// ============================================================\n\n/**\n * Supported agent/IDE rule sources.\n * All 7 major AI IDEs are supported.\n */\nexport type RuleSource =\n | 'cursor'\n | 'claude-code'\n | 'codex'\n | 'windsurf'\n | 'antigravity'\n | 'gemini-cli'\n | 'copilot'\n | 'kiro'\n | 'trae'\n | 'memorix';\n\n/** A parsed rule in the unified intermediate representation */\nexport interface UnifiedRule {\n id: string;\n content: string;\n description?: string;\n source: RuleSource;\n scope: 'global' | 'project' | 'path-specific';\n paths?: string[];\n alwaysApply?: boolean;\n priority: number;\n hash: string;\n}\n\n/**\n * Format adapter interface — implement this for each agent/IDE.\n * Adding a new agent (e.g., Kiro) only requires implementing this interface.\n */\nexport interface RuleFormatAdapter {\n /** Unique identifier for this agent format */\n readonly source: RuleSource;\n\n /** File paths/globs this adapter can parse */\n readonly filePatterns: string[];\n\n /** Parse rule files into unified representation */\n parse(filePath: string, content: string): UnifiedRule[];\n\n /** Generate rule file content from unified representation */\n generate(rules: UnifiedRule[]): { filePath: string; content: string }[];\n}\n\n// ============================================================\n// Project Identity\n// ============================================================\n\nexport interface ProjectInfo {\n id: string;\n name: string;\n gitRemote?: string;\n rootPath: string;\n}\n\n/**\n * Diagnostic failure info from project detection.\n * Tells callers exactly WHY detection failed so they can report actionable errors.\n */\nexport type DetectionFailureReason =\n | 'path_not_found'\n | 'not_a_directory'\n | 'no_git'\n | 'git_worktree_error'\n | 'git_safe_directory'\n | 'remote_resolve_failed';\n\nexport interface DetectionFailure {\n reason: DetectionFailureReason;\n path: string;\n detail: string;\n}\n\nexport interface DetectionResult {\n project: ProjectInfo | null;\n failure: DetectionFailure | null;\n}\n\n// ============================================================\n// Memorix Server Configuration\n// ============================================================\n\nexport interface MemorixConfig {\n dataDir: string;\n projectId: string;\n projectName: string;\n enableEmbeddings: boolean;\n enableRulesSync: boolean;\n watchRuleFiles: boolean;\n}\n\nexport const DEFAULT_CONFIG: Partial<MemorixConfig> = {\n enableEmbeddings: false,\n enableRulesSync: false,\n watchRuleFiles: false,\n};\n\n// ============================================================\n// Workspace Sync — Cross-Agent workspace migration\n// ============================================================\n\n/** Supported agent targets for workspace sync */\nexport type AgentTarget =\n | 'windsurf'\n | 'cursor'\n | 'claude-code'\n | 'codex'\n | 'copilot'\n | 'antigravity'\n | 'gemini-cli'\n | 'openclaw'\n | 'hermes'\n | 'omp'\n | 'kiro'\n | 'opencode'\n | 'trae';\n\n/** A unified MCP server entry across all agent config formats */\nexport interface MCPServerEntry {\n name: string;\n /** Command for stdio transport */\n command: string;\n /** Args for stdio transport */\n args: string[];\n /** Environment variables */\n env?: Record<string, string> | null;\n /** URL for HTTP/SSE transport (Codex uses `url`, Windsurf uses `serverUrl`) */\n url?: string;\n /** HTTP headers (Windsurf uses `headers` for HTTP transport) */\n headers?: Record<string, string>;\n /** Whether this server is disabled */\n disabled?: boolean;\n /** Claude Code: force small core tool sets to load before the first prompt */\n alwaysLoad?: boolean;\n}\n\n/** Unified workflow entry */\nexport interface WorkflowEntry {\n name: string;\n description: string;\n content: string;\n source: AgentTarget;\n filePath: string;\n}\n\n/** A skill folder discovered from an agent's skills directory */\nexport interface SkillEntry {\n name: string;\n description: string;\n sourcePath: string;\n sourceAgent: AgentTarget;\n}\n\n/** Conflict when two agents have a skill with the same folder name */\nexport interface SkillConflict {\n name: string;\n kept: SkillEntry;\n skipped: SkillEntry;\n}\n\n/** Result of a workspace sync operation */\nexport interface WorkspaceSyncResult {\n mcpServers: {\n scanned: MCPServerEntry[];\n generated: { filePath: string; content: string }[];\n };\n workflows: {\n scanned: WorkflowEntry[];\n generated: { filePath: string; content: string }[];\n };\n rules: {\n scanned: number;\n generated: number;\n };\n skills: {\n scanned: SkillEntry[];\n conflicts: SkillConflict[];\n copied: string[];\n skipped: string[];\n };\n}\n\n// ============================================================\n// Mini-Skills — Promoted memories that never decay\n// ============================================================\n\n/** A mini-skill promoted from one or more observations */\nexport interface MiniSkill {\n id: number;\n /** Observation IDs this mini-skill was derived from (live refs, best-effort) */\n sourceObservationIds: number[];\n /** Entity the source observations belong to */\n sourceEntity: string;\n /** Short title for the skill */\n title: string;\n /** What the agent should do (imperative instruction) */\n instruction: string;\n /** When this skill should be applied (scenario description) */\n trigger: string;\n /** Key facts extracted from source observations */\n facts: string[];\n /** Project this skill belongs to */\n projectId: string;\n /** ISO timestamp */\n createdAt: string;\n /** How many times this skill was injected in session_start */\n usedCount: number;\n /** Classification tags */\n tags: string[];\n /** Frozen source observation content at promote time (JSON, immutable provenance proof) */\n sourceSnapshot?: string;\n /** ISO timestamp of last modification (Phase 3a: set once at creation) */\n updatedAt?: string;\n}\n\n// ============================================================\n// Source Snapshot — immutable provenance proof for promoted knowledge\n// ============================================================\n\n/** A single observation entry within a source snapshot */\nexport interface SnapshotObservation {\n id: number;\n title: string;\n type: string;\n narrative: string;\n facts: string[];\n entityName: string;\n projectId: string;\n createdAt: string;\n /** Frozen source detail for provenance (explicit / hook / git-ingest) */\n sourceDetail?: string;\n}\n\n/** Frozen source content captured at promote time */\nexport interface SourceSnapshot {\n observations: SnapshotObservation[];\n promotedAt: string;\n}\n\n// ============================================================\n// Knowledge Layer — Phase 3a retrieval classification\n// ============================================================\n\n/** Classification of knowledge for layer-aware ranking */\nexport type KnowledgeLayer = 'project-truth' | 'promoted' | 'evidence';\n\n/** Document type discriminator for Orama index */\nexport type DocumentType = 'observation' | 'mini-skill';\n\n// ============================================================\n// Typed Memory Reference — Phase 3a reference protocol\n// ============================================================\n\n/** A typed reference to a memory object (observation or mini-skill) */\nexport interface MemoryRef {\n kind: 'obs' | 'skill';\n id: number;\n projectId?: string;\n}\n\n/** MCP config format adapter interface */\nexport interface MCPConfigAdapter {\n readonly source: AgentTarget;\n /** Parse MCP server entries from a config file */\n parse(content: string): MCPServerEntry[];\n /** Generate config file content from MCP server entries */\n generate(servers: MCPServerEntry[]): string;\n /** Get the default config file path for this agent */\n getConfigPath(projectRoot?: string): string;\n}\n"],"mappings":";AAsEO,IAAM,oBAAqD;AAAA,EAChE,mBAAmB;AAAA,EACnB,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS;AACX;AA4LO,IAAM,qBAA+C;AAAA,EAC1D,gBAAgB,CAAC,gBAAgB,UAAU,OAAO,aAAa,SAAS;AAAA,EACxE,OAAO,CAAC,UAAU,OAAO,SAAS,cAAc,SAAS,kBAAkB;AAAA,EAC3E,YAAY,CAAC,YAAY,aAAa,UAAU,UAAU;AAAA,EAC1D,UAAU,CAAC,UAAU,SAAS,OAAO,eAAe,YAAY;AAAA,EAChE,aAAa,CAAC,aAAa,YAAY,WAAW,QAAQ;AAAA,EAC1D,WAAW,CAAC,WAAW,cAAc,YAAY,eAAe;AAClE;AAoJO,IAAM,iBAAyC;AAAA,EACpD,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,gBAAgB;AAClB;","names":[]}
@@ -0,0 +1,85 @@
1
+ # 1.2.5 Retrieval Performance and Reliability
2
+
3
+ ## Goal
4
+
5
+ Make retrieval predictable for both people and agents:
6
+
7
+ - a normal search must not wait on optional LLM work;
8
+ - a finished search must not leave a timeout timer alive and delay CLI exit;
9
+ - embedding credentials must not silently become LLM credentials;
10
+ - measured performance claims must identify the exact runtime being measured.
11
+
12
+ This release improves the existing local SQLite + in-process Orama design. It
13
+ does not introduce a daemon-only retrieval path, a new vector database, or a
14
+ cross-process shared Orama index.
15
+
16
+ ## Retrieval Profiles
17
+
18
+ | Profile | Use case | Local retrieval | Embeddings | LLM query rewrite / rerank |
19
+ | --- | --- | --- | --- | --- |
20
+ | `fast` | Scripts, probes, latency-sensitive agents | Yes | No | No |
21
+ | `balanced` (default) | Everyday memory search | Yes | When configured | No |
22
+ | `thorough` | An explicit, higher-quality investigation | Yes | When configured | Yes, only with an explicit memory LLM lane |
23
+
24
+ `fast` never makes a network request and uses non-fuzzy lexical matching without
25
+ vector payloads or fuzzy typo expansion. `balanced` preserves semantic vector
26
+ search, typo tolerance, and intent-aware ranking but keeps optional LLM
27
+ enrichment out of the default critical path.
28
+ `thorough` is opt-in because it can add provider latency and cost.
29
+
30
+ The profile is available through the SDK, `memorix memory search --quality`,
31
+ the top-level `memorix search --quality` shortcut, and the `memorix_search` MCP
32
+ tool. Existing callers that omit it receive `balanced` behavior.
33
+
34
+ ## Configuration Lanes
35
+
36
+ Memorix has separate agent, memory-LLM, and embedding lanes. In particular:
37
+
38
+ - `OPENROUTER_API_KEY` remains a compatible fallback for an embedding endpoint
39
+ hosted by OpenRouter.
40
+ - It is not a generic memory-LLM credential.
41
+ - It may supply the memory-LLM key only when `[memory.llm] provider =
42
+ "openrouter"`, or that lane explicitly points at an OpenRouter base URL.
43
+
44
+ This prevents an embedding-only configuration from unexpectedly enabling LLM
45
+ query rewriting or reranking.
46
+
47
+ ## Timeout Contract
48
+
49
+ Every timeout helper must clear its timer when the protected operation settles.
50
+ That applies to embedding requests, LLM reranking, MCP tool budgets, and
51
+ background maintenance. This prevents a completed CLI command from being kept
52
+ alive solely by a timeout watchdog while preserving a real deadline for a
53
+ genuinely stalled operation.
54
+
55
+ Timeouts still bound callers; they do not cancel a provider request that lacks
56
+ an abort signal. The caller receives the fallback/error immediately, while the
57
+ underlying request is allowed to settle safely in the background.
58
+
59
+ ## Evidence Protocol
60
+
61
+ `npm run benchmark:retrieval` measures hot, in-process lexical retrieval with
62
+ embeddings and LLM work disabled. It reports index-build time plus separate
63
+ p50/p95/p99 values for a specific lookup and a broad query over a deterministic
64
+ synthetic corpus. The two query shapes are intentionally not averaged together.
65
+
66
+ It is deliberately not an end-to-end CLI, MCP, remote embedding, or LLM
67
+ benchmark. Those paths have different startup, IPC, network, and provider
68
+ costs and must be reported separately.
69
+
70
+ Example:
71
+
72
+ ```powershell
73
+ npm run build
74
+ npm run benchmark:retrieval -- --records 1000 --runs 100
75
+ ```
76
+
77
+ ## Verification Gates
78
+
79
+ 1. Focused timeout, config, semantic-search, CLI, SDK, and MCP tests pass.
80
+ 2. The packaged CLI smoke verifies `fast`, default `balanced`, and
81
+ `thorough` argument handling without changing stored data.
82
+ 3. The MCP smoke verifies the `quality` schema and confirms no completed
83
+ search leaves a watchdog timer behind.
84
+ 4. Benchmark output is retained as evidence with its mode and machine context,
85
+ never presented as a universal latency guarantee.
@@ -14,6 +14,24 @@ Memorix is designed to be light for everyday memory use and explicit about heavi
14
14
 
15
15
  The default memory path uses local SQLite as the canonical store and Orama for search/indexing. No cloud service is required.
16
16
 
17
+ ## Retrieval Profiles
18
+
19
+ | Profile | Default? | Network work | Best fit |
20
+ | --- | --- | --- | --- |
21
+ | `fast` | No | Never | Scripts, probes, latency-sensitive agent steps |
22
+ | `balanced` | Yes | Optional embedding only | Everyday search |
23
+ | `thorough` | No | Optional embedding plus explicitly enabled memory LLM work | A deliberate deep investigation |
24
+
25
+ Use `--quality fast|balanced|thorough` with `memorix memory search` or
26
+ `memorix search`; the `memorix_search` MCP tool and SDK expose the same field.
27
+ `fast` is fully local. `balanced` keeps optional LLM query rewriting and
28
+ reranking out of the normal search path. `thorough` is opt-in because it can
29
+ add provider latency and cost.
30
+
31
+ `OPENROUTER_API_KEY` may provide an API key for an OpenRouter embedding endpoint.
32
+ It does not enable the memory-LLM lane unless that lane explicitly selects
33
+ OpenRouter through its provider or base URL.
34
+
17
35
  ## Interactive and Maintenance Boundaries
18
36
 
19
37
  MCP transport readiness is separate from full search-index readiness. The first
@@ -58,6 +76,8 @@ On the release development machine used for this check, the healthy HTTP service
58
76
  | `MEMORIX_LLM_API_KEY` / `OPENAI_API_KEY` | unset | Enable LLM-backed enrichment, extraction, rerank, or skill generation |
59
77
  | `MEMORIX_LLM_TIMEOUT_MS` | `30000` (30 s) | Bound a single LLM-backed extraction/resolve call |
60
78
  | `MEMORIX_RERANK_TIMEOUT_MS` | provider default | Bound slow LLM rerank calls |
79
+ | `memorix memory search --quality fast` | n/a | Force a fully local retrieval path for a latency-sensitive call |
80
+ | `npm run benchmark:retrieval -- --records 1000 --runs 100` | n/a | Reproduce hot in-process lexical retrieval latency; not an end-to-end claim |
61
81
  | `[codegraph].max_file_bytes` | `2097152` | Raise only when a large file is intentional source that should enter Code Memory |
62
82
  | `memorix retention status` | report only | Inspect whether memory growth needs cleanup |
63
83
  | `memorix retention archive` | explicit | Archive expired memories when the project gets noisy |
@@ -71,6 +91,7 @@ On the release development machine used for this check, the healthy HTTP service
71
91
  - For Docker, use it when you want a managed HTTP service. Do not use image size alone as the runtime memory estimate.
72
92
  - For orchestrated subagent work, expect CPU and disk activity proportional to the spawned agents and verification commands.
73
93
  - For release checks, measure build/test/pack separately from idle service cost.
94
+ - When comparing retrieval latency, report cold CLI, warm in-process SDK, MCP/HTTP, remote embedding, and LLM-enhanced paths separately. They have materially different costs.
74
95
  - When Dashboard shows queued or failed maintenance work, inspect
75
96
  `/api/maintenance` on that local dashboard before assuming a Code Memory scan
76
97
  or lifecycle task completed.
@@ -79,7 +100,7 @@ On the release development machine used for this check, the healthy HTTP service
79
100
 
80
101
  These are not release blockers, but they are reasonable future improvements:
81
102
 
82
- - Add a lightweight benchmark command that reports startup time, index size, SQLite size, and search latency.
103
+ - Extend the retrieval benchmark with separately labelled cold CLI, MCP, and remote-provider cases when a release needs those comparisons.
83
104
  - Add dashboard-side performance telemetry for API latency and payload sizes.
84
105
  - Document recommended retention schedules for large projects.
85
106
  - Evaluate a persistent cross-process retrieval index only as a major-version
@@ -4,14 +4,39 @@
4
4
  > older notes when they conflict.
5
5
 
6
6
  ## Current State
7
- - Phase: 1.2.4 persistent memory delivery release candidate
8
- - Branch: `codex/1.2.4-persistent-memory-delivery`
9
- - Last updated: 2026-07-26
10
- - Released baseline: `memorix@1.2.3`, tag `v1.2.3`, `origin/main` at
11
- `03ceb6d`.
12
- - Goal: make the existing persistent memory layer deliver useful prior work
13
- naturally across MCP and CLI without turning ordinary tasks into historical
14
- context dumps.
7
+ - Phase: 1.2.5 retrieval performance and reliability release candidate
8
+ - Branch: `codex/1.2.5-retrieval-performance`
9
+ - Last updated: 2026-07-27
10
+ - Released baseline: `memorix@1.2.4`, tag `v1.2.4`, `origin/main` at
11
+ `24f3b9b`.
12
+ - Goal: make ordinary retrieval deterministic and bounded without changing the
13
+ local SQLite + in-process Orama architecture.
14
+
15
+ ## 1.2.5 Retrieval Performance and Reliability
16
+ - Retrieval now has three explicit profiles: `fast` is fully local, `balanced`
17
+ is the default vector-capable path without LLM query work, and `thorough`
18
+ explicitly opts into configured LLM query rewrite/reranking.
19
+ - `OPENROUTER_API_KEY` remains an embedding fallback only. It activates the
20
+ memory LLM lane only when that lane explicitly names an OpenRouter provider
21
+ or base URL, preventing embedding-only configurations from adding hidden
22
+ network work to normal search.
23
+ - Embedding, rerank, MCP search, and maintenance share one timeout helper that
24
+ clears watchdog timers after settlement. This removes the previously measured
25
+ avoidable CLI exit delay from a completed rerank path while preserving real
26
+ timeouts for stalled work.
27
+ - Read-only memory CLI commands skip session-store and maintenance-target setup
28
+ while retaining the existing project/identity visibility boundary.
29
+ - Local verification completed before release: TypeScript check; focused
30
+ timeout/config/search/CLI/SDK tests; a full serial suite that reached a
31
+ single stale Codex plugin-manifest version assertion, followed by its
32
+ dedicated passing recheck after the template was synced; production build;
33
+ package SDK benchmark; real CLI smoke; and an in-memory MCP client smoke
34
+ confirming `memorix_search.quality` and `fast` mode.
35
+ - Current benchmark evidence on Windows/Node 22: a 1,000-record hot in-process
36
+ SDK lexical fixture measured specific lookup p50 44.603 ms / p95 96.085 ms
37
+ and broad-query p50 160.903 ms / p95 171.878 ms. This excludes CLI startup,
38
+ MCP transport, remote embeddings, and LLM work by design; the two query
39
+ shapes are reported separately rather than averaged into a marketing number.
15
40
 
16
41
  ## 1.2.4 Persistent Memory Delivery
17
42
  - Continuation is now a bounded delivery projection over existing sessions and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -62,6 +62,7 @@
62
62
  "test:llm-live": "MEMORIX_RUN_LIVE_LLM_TESTS=1 vitest run tests/integration/formation-llm-quality.test.ts",
63
63
  "test:e2e": "vitest run tests/e2e/",
64
64
  "benchmark:codegraph": "node scripts/benchmark-codegraph-provider.cjs",
65
+ "benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
65
66
  "lint": "tsc --noEmit",
66
67
  "prepublishOnly": "npm run build && npm test"
67
68
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -7,8 +7,10 @@ import { canManageObservation, filterReadableObservations, resolveObservationVis
7
7
  import {
8
8
  coerceObservationStatus,
9
9
  coerceObservationType,
10
+ coerceRetrievalQuality,
10
11
  emitError,
11
12
  emitResult,
13
+ getCliReadContext,
12
14
  getCliProjectContext,
13
15
  parseCsvList,
14
16
  parsePositiveInt,
@@ -36,6 +38,7 @@ export default defineCommand({
36
38
  topicKey: { type: 'string', description: 'Stable topic key override' },
37
39
  action: { type: 'string', description: 'Secondary action for advanced memory commands' },
38
40
  limit: { type: 'string', description: 'Limit for search/recent output' },
41
+ quality: { type: 'string', description: 'Retrieval profile: fast, balanced (default), or thorough' },
39
42
  graphLimit: { type: 'string', description: 'Limit for graph-context output' },
40
43
  graphQuery: { type: 'string', description: 'Query for graph-context packet' },
41
44
  format: { type: 'string', description: 'Output format for graph-context: summary or prompt' },
@@ -56,7 +59,12 @@ export default defineCommand({
56
59
  const asJson = !!args.json;
57
60
 
58
61
  try {
59
- const { project, dataDir, reader, identity } = await getCliProjectContext({ searchIndex: true });
62
+ const readOnlyActions = new Set(['', 'search', 'graph-context', 'recent', 'suggest-topic-key', 'detail', 'timeline']);
63
+ const needsSearchIndex = action === 'search' || action === 'detail' || action === 'timeline';
64
+ const context = readOnlyActions.has(action)
65
+ ? await getCliReadContext({ searchIndex: needsSearchIndex })
66
+ : await getCliProjectContext({ searchIndex: true });
67
+ const { project, dataDir, reader, identity } = context;
60
68
 
61
69
  switch (action) {
62
70
  case 'search': {
@@ -66,7 +74,8 @@ export default defineCommand({
66
74
  return;
67
75
  }
68
76
  const limit = parsePositiveInt(args.limit as string | undefined, 10);
69
- const result = await compactSearch({ query, limit, projectId: project.id, reader });
77
+ const quality = coerceRetrievalQuality(args.quality as string | undefined);
78
+ const result = await compactSearch({ query, limit, quality, projectId: project.id, reader });
70
79
  emitResult({ project, entries: result.entries }, result.formatted, asJson);
71
80
  return;
72
81
  }
@@ -420,7 +429,7 @@ export default defineCommand({
420
429
  console.log('Memorix Memory Commands');
421
430
  console.log('');
422
431
  console.log('Usage:');
423
- console.log(' memorix memory search --query "timeout bug" [--limit 10]');
432
+ console.log(' memorix memory search --query "timeout bug" [--limit 10] [--quality fast|balanced|thorough]');
424
433
  console.log(' memorix memory recent [--limit 10]');
425
434
  console.log(' memorix memory store --text "..." [--title "..."] [--type discovery] [--visibility project|personal|team]');
426
435
  console.log(' memorix memory suggest-topic-key --type decision --title "..."');
@@ -9,9 +9,10 @@ import type {
9
9
  ObservationStatus,
10
10
  ObservationType,
11
11
  ObservationVisibility,
12
+ RetrievalQuality,
12
13
  } from '../../types.js';
13
14
  import { getCliInvocation } from '../invocation.js';
14
- import { resolveCliIdentity, type CliIdentity } from '../identity.js';
15
+ import { loadCliIdentity, resolveCliIdentity, type CliIdentity } from '../identity.js';
15
16
 
16
17
  export interface CliProjectContext {
17
18
  project: ProjectInfo;
@@ -22,7 +23,24 @@ export interface CliProjectContext {
22
23
  identityWarning?: string;
23
24
  }
24
25
 
25
- export async function getCliProjectContext(options?: { searchIndex?: boolean; projectRoot?: string }): Promise<CliProjectContext> {
26
+ export interface CliReadContext {
27
+ project: ProjectInfo;
28
+ dataDir: string;
29
+ reader: ObservationReader;
30
+ identity: CliIdentity | null;
31
+ identityWarning?: string;
32
+ }
33
+
34
+ interface CliContextOptions {
35
+ searchIndex?: boolean;
36
+ projectRoot?: string;
37
+ }
38
+
39
+ async function resolveCliProjectContext(options?: CliContextOptions): Promise<{
40
+ project: ProjectInfo;
41
+ dataDir: string;
42
+ invocation: ReturnType<typeof getCliInvocation>;
43
+ }> {
26
44
  const invocation = getCliInvocation();
27
45
  const detection = detectProjectWithDiagnostics(
28
46
  options?.projectRoot
@@ -37,6 +55,51 @@ export async function getCliProjectContext(options?: { searchIndex?: boolean; pr
37
55
 
38
56
  const project = detection.project;
39
57
  const dataDir = await getProjectDataDir(project.id);
58
+ return { project, dataDir, invocation };
59
+ }
60
+
61
+ /**
62
+ * Read-only memory commands do not need session bookkeeping or maintenance
63
+ * target registration. Identity resolution remains intact so visibility rules
64
+ * stay identical to the full operator context.
65
+ */
66
+ export async function getCliReadContext(options?: CliContextOptions): Promise<CliReadContext> {
67
+ const { project, dataDir, invocation } = await resolveCliProjectContext(options);
68
+ await initObservations(dataDir);
69
+
70
+ if (options?.searchIndex) {
71
+ await prepareSearchIndex();
72
+ }
73
+
74
+ const storedIdentity = await loadCliIdentity(dataDir, project.id);
75
+ if (!invocation.actorId && !storedIdentity) {
76
+ return {
77
+ project,
78
+ dataDir,
79
+ reader: { projectId: project.id },
80
+ identity: null,
81
+ };
82
+ }
83
+
84
+ const teamStore = await initTeamStore(dataDir);
85
+ const identity = await resolveCliIdentity({
86
+ project,
87
+ dataDir,
88
+ teamStore,
89
+ explicitActorId: invocation.actorId,
90
+ });
91
+
92
+ return {
93
+ project,
94
+ dataDir,
95
+ reader: identity.reader,
96
+ identity: identity.identity,
97
+ ...(identity.warning ? { identityWarning: identity.warning } : {}),
98
+ };
99
+ }
100
+
101
+ export async function getCliProjectContext(options?: CliContextOptions): Promise<CliProjectContext> {
102
+ const { project, dataDir, invocation } = await resolveCliProjectContext(options);
40
103
  try {
41
104
  const { MaintenanceTargetStore } = await import('../../runtime/maintenance-targets.js');
42
105
  new MaintenanceTargetStore(dataDir).register({
@@ -129,6 +192,7 @@ const OBSERVATION_TYPES: ObservationType[] = [
129
192
  ];
130
193
 
131
194
  const OBSERVATION_STATUSES: ObservationStatus[] = ['active', 'resolved', 'archived'];
195
+ const RETRIEVAL_QUALITIES: RetrievalQuality[] = ['fast', 'balanced', 'thorough'];
132
196
 
133
197
  export function coerceObservationType(input?: string): ObservationType {
134
198
  const normalized = (input ?? 'discovery') as ObservationType;
@@ -150,6 +214,14 @@ export function coerceObservationStatus(input?: string): ObservationStatus {
150
214
  return normalized;
151
215
  }
152
216
 
217
+ export function coerceRetrievalQuality(input?: string): RetrievalQuality {
218
+ const normalized = (input ?? 'balanced').trim().toLowerCase() as RetrievalQuality;
219
+ if (!RETRIEVAL_QUALITIES.includes(normalized)) {
220
+ throw new Error('quality must be fast, balanced, or thorough');
221
+ }
222
+ return normalized;
223
+ }
224
+
153
225
  export function coerceObservationVisibility(input?: string): ObservationVisibility {
154
226
  const normalized = (input ?? 'project').trim().toLowerCase();
155
227
  if (normalized === 'personal' || normalized === 'project' || normalized === 'team') {
package/src/cli/index.ts CHANGED
@@ -985,12 +985,14 @@ const main = defineCommand({
985
985
  args: {
986
986
  query: { type: 'positional', description: 'Search query', required: true },
987
987
  limit: { type: 'string', description: 'Maximum results' },
988
+ quality: { type: 'string', description: 'Retrieval profile: fast, balanced (default), or thorough' },
988
989
  json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
989
990
  },
990
991
  async run({ args }) {
991
992
  await runMemoryShortcut('search', {
992
993
  query: args.query,
993
994
  limit: args.limit,
995
+ quality: args.quality,
994
996
  json: args.json,
995
997
  });
996
998
  },
@@ -88,6 +88,27 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
88
88
  const legacy = loadFileConfig();
89
89
  const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
90
90
  const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : undefined;
91
+ const memoryLlmProvider = first(
92
+ process.env.MEMORIX_LLM_PROVIDER,
93
+ toml.memory?.llm?.provider,
94
+ yaml.llm?.provider,
95
+ legacy.llm?.provider,
96
+ );
97
+ const memoryLlmModel = first(
98
+ process.env.MEMORIX_LLM_MODEL,
99
+ toml.memory?.llm?.model,
100
+ yaml.llm?.model,
101
+ legacy.llm?.model,
102
+ );
103
+ const memoryLlmBaseUrl = first(
104
+ process.env.MEMORIX_LLM_BASE_URL,
105
+ toml.memory?.llm?.base_url,
106
+ yaml.llm?.baseUrl,
107
+ legacy.llm?.baseUrl,
108
+ );
109
+ const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl)
110
+ ? process.env.OPENROUTER_API_KEY
111
+ : undefined;
91
112
 
92
113
  const resolved: ResolvedMemorixConfig = {
93
114
  agent: {
@@ -126,9 +147,9 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
126
147
  autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml.behavior?.autoCleanup),
127
148
  syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml.behavior?.syncAdvisory),
128
149
  llm: {
129
- provider: first(process.env.MEMORIX_LLM_PROVIDER, toml.memory?.llm?.provider, yaml.llm?.provider, legacy.llm?.provider),
130
- model: first(process.env.MEMORIX_LLM_MODEL, toml.memory?.llm?.model, yaml.llm?.model, legacy.llm?.model),
131
- baseUrl: first(process.env.MEMORIX_LLM_BASE_URL, toml.memory?.llm?.base_url, yaml.llm?.baseUrl, legacy.llm?.baseUrl),
150
+ provider: memoryLlmProvider,
151
+ model: memoryLlmModel,
152
+ baseUrl: memoryLlmBaseUrl,
132
153
  apiKey: first(
133
154
  process.env.MEMORIX_LLM_API_KEY,
134
155
  process.env.MEMORIX_API_KEY,
@@ -137,7 +158,7 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
137
158
  legacy.llm?.apiKey,
138
159
  process.env.OPENAI_API_KEY,
139
160
  process.env.ANTHROPIC_API_KEY,
140
- process.env.OPENROUTER_API_KEY,
161
+ openRouterMemoryLlmApiKey,
141
162
  ),
142
163
  },
143
164
  },
@@ -290,6 +311,10 @@ function isOpenRouterUrl(value: string | undefined): boolean {
290
311
  }
291
312
  }
292
313
 
314
+ function isOpenRouterMemoryLane(provider: string | undefined, baseUrl: string | undefined): boolean {
315
+ return provider?.trim().toLowerCase() === 'openrouter' || isOpenRouterUrl(baseUrl);
316
+ }
317
+
293
318
  function normalizeExternalContext(value: string | undefined): 'auto' | 'off' | undefined {
294
319
  const normalized = value?.trim().toLowerCase();
295
320
  if (normalized === 'auto' || normalized === 'off') return normalized;
@@ -9,6 +9,7 @@ import {
9
9
  enqueueObservationQualification,
10
10
  type MaintenanceQueue,
11
11
  } from './lifecycle.js';
12
+ import { withTimeout } from '../timeout.js';
12
13
 
13
14
  const DEFAULT_VECTOR_BATCH_SIZE = 12;
14
15
  const DEFAULT_RETENTION_BATCH_SIZE = 100;
@@ -110,20 +111,6 @@ async function loadWorkspaceForMaintenance(
110
111
  return versioned ?? local;
111
112
  }
112
113
 
113
- /**
114
- * Creates handlers for one initialized project runtime. The worker itself is
115
- * project-scoped so it never claims a different project's queued work.
116
- */
117
- function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
118
- return new Promise<T>((resolve, reject) => {
119
- const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
120
- promise.then(
121
- (value) => { clearTimeout(timer); resolve(value); },
122
- (error) => { clearTimeout(timer); reject(error); },
123
- );
124
- });
125
- }
126
-
127
114
  async function runAutomaticConsolidation(
128
115
  projectId: string,
129
116
  projectDir: string,
package/src/sdk.ts CHANGED
@@ -26,6 +26,7 @@ import type {
26
26
  ProjectInfo,
27
27
  DetectionResult,
28
28
  ObservationReader,
29
+ RetrievalQuality,
29
30
  } from './types.js';
30
31
  import {
31
32
  canManageObservation,
@@ -90,6 +91,8 @@ export interface ClientSearchOptions {
90
91
  status?: ObservationStatus | 'all';
91
92
  /** Maximum results. Default: 20 */
92
93
  limit?: number;
94
+ /** Retrieval profile. Default: balanced. */
95
+ quality?: RetrievalQuality;
93
96
  }
94
97
 
95
98
  /** Result from resolving observations */
@@ -220,6 +223,7 @@ export class MemoryClient {
220
223
  type: options.type,
221
224
  source: options.source,
222
225
  status: options.status === 'all' ? undefined : (options.status ?? 'active'),
226
+ quality: options.quality,
223
227
  reader: this._reader,
224
228
  };
225
229