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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.2.5] - 2026-07-27
6
+
7
+ ### Added
8
+ - **Explicit retrieval profiles** -- Search now accepts `fast`, `balanced` (default), or `thorough` through the CLI, MCP, and SDK. Fast is fully local, balanced keeps optional LLM work out of everyday retrieval, and thorough explicitly opts into configured LLM query refinement.
9
+ - **Reproducible retrieval evidence** -- Added `npm run benchmark:retrieval` for a clearly-labelled hot in-process SDK lexical benchmark with p50/p95/p99 output. It does not present CLI startup, MCP transport, remote embedding, or LLM latency as the same metric.
10
+
11
+ ### Changed
12
+ - **Lighter read-only CLI path** -- Memory search, detail, timeline, recent, graph context, and help no longer create session bookkeeping or maintenance-target metadata. Identity and visibility checks remain unchanged.
13
+
14
+ ### Fixed
15
+ - **Completed searches no longer retain timeout watchdogs** -- Embedding, rerank, MCP search, and maintenance timeout paths share a helper that clears the timer when work settles. This removes the avoidable CLI exit delay caused by an already-completed optional rerank.
16
+ - **OpenRouter lane isolation** -- An embedding-only `OPENROUTER_API_KEY` no longer silently enables the memory LLM lane. It remains valid for a memory LLM only when that lane explicitly selects an OpenRouter provider or base URL.
17
+
5
18
  ## [1.2.4] - 2026-07-26
6
19
 
7
20
  ### Added
package/dist/cli/index.js CHANGED
@@ -3212,7 +3212,7 @@ ${import_picocolors.default.gray(m3)} ${s2}
3212
3212
 
3213
3213
  // src/cli/version.ts
3214
3214
  function getCliVersion() {
3215
- return true ? "1.2.4" : pkg.version;
3215
+ return true ? "1.2.5" : pkg.version;
3216
3216
  }
3217
3217
  var init_version = __esm({
3218
3218
  "src/cli/version.ts"() {
@@ -13763,6 +13763,25 @@ function getResolvedConfig(options2 = {}) {
13763
13763
  const legacy = loadFileConfig();
13764
13764
  const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml4.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
13765
13765
  const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
13766
+ const memoryLlmProvider = first(
13767
+ process.env.MEMORIX_LLM_PROVIDER,
13768
+ toml.memory?.llm?.provider,
13769
+ yaml4.llm?.provider,
13770
+ legacy.llm?.provider
13771
+ );
13772
+ const memoryLlmModel = first(
13773
+ process.env.MEMORIX_LLM_MODEL,
13774
+ toml.memory?.llm?.model,
13775
+ yaml4.llm?.model,
13776
+ legacy.llm?.model
13777
+ );
13778
+ const memoryLlmBaseUrl = first(
13779
+ process.env.MEMORIX_LLM_BASE_URL,
13780
+ toml.memory?.llm?.base_url,
13781
+ yaml4.llm?.baseUrl,
13782
+ legacy.llm?.baseUrl
13783
+ );
13784
+ const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
13766
13785
  const resolved = {
13767
13786
  agent: {
13768
13787
  provider: first(
@@ -13800,9 +13819,9 @@ function getResolvedConfig(options2 = {}) {
13800
13819
  autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml4.behavior?.autoCleanup),
13801
13820
  syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml4.behavior?.syncAdvisory),
13802
13821
  llm: {
13803
- provider: first(process.env.MEMORIX_LLM_PROVIDER, toml.memory?.llm?.provider, yaml4.llm?.provider, legacy.llm?.provider),
13804
- model: first(process.env.MEMORIX_LLM_MODEL, toml.memory?.llm?.model, yaml4.llm?.model, legacy.llm?.model),
13805
- baseUrl: first(process.env.MEMORIX_LLM_BASE_URL, toml.memory?.llm?.base_url, yaml4.llm?.baseUrl, legacy.llm?.baseUrl),
13822
+ provider: memoryLlmProvider,
13823
+ model: memoryLlmModel,
13824
+ baseUrl: memoryLlmBaseUrl,
13806
13825
  apiKey: first(
13807
13826
  process.env.MEMORIX_LLM_API_KEY,
13808
13827
  process.env.MEMORIX_API_KEY,
@@ -13811,7 +13830,7 @@ function getResolvedConfig(options2 = {}) {
13811
13830
  legacy.llm?.apiKey,
13812
13831
  process.env.OPENAI_API_KEY,
13813
13832
  process.env.ANTHROPIC_API_KEY,
13814
- process.env.OPENROUTER_API_KEY
13833
+ openRouterMemoryLlmApiKey
13815
13834
  )
13816
13835
  }
13817
13836
  },
@@ -13948,6 +13967,9 @@ function isOpenRouterUrl(value) {
13948
13967
  return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
13949
13968
  }
13950
13969
  }
13970
+ function isOpenRouterMemoryLane(provider2, baseUrl) {
13971
+ return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
13972
+ }
13951
13973
  function normalizeExternalContext(value) {
13952
13974
  const normalized = value?.trim().toLowerCase();
13953
13975
  if (normalized === "auto" || normalized === "off") return normalized;
@@ -15995,6 +16017,29 @@ Rules:
15995
16017
  }
15996
16018
  });
15997
16019
 
16020
+ // src/timeout.ts
16021
+ function withTimeout(promise, ms, label) {
16022
+ return new Promise((resolve6, reject) => {
16023
+ const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
16024
+ promise.then(
16025
+ (value) => {
16026
+ clearTimeout(timer);
16027
+ resolve6(value);
16028
+ },
16029
+ (error2) => {
16030
+ clearTimeout(timer);
16031
+ reject(error2);
16032
+ }
16033
+ );
16034
+ });
16035
+ }
16036
+ var init_timeout = __esm({
16037
+ "src/timeout.ts"() {
16038
+ "use strict";
16039
+ init_esm_shims();
16040
+ }
16041
+ });
16042
+
15998
16043
  // src/project/aliases.ts
15999
16044
  var aliases_exports = {};
16000
16045
  __export(aliases_exports, {
@@ -19599,8 +19644,12 @@ async function searchObservations(options2) {
19599
19644
  }
19600
19645
  };
19601
19646
  const modeKey = options2.projectId ?? SEARCH_MODE_DEFAULT_KEY;
19602
- lastSearchModeByProject.set(modeKey, embeddingEnabled ? "hybrid" : "fulltext");
19647
+ const quality = options2.quality ?? "balanced";
19603
19648
  const database = await getDb();
19649
+ lastSearchModeByProject.set(
19650
+ modeKey,
19651
+ quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
19652
+ );
19604
19653
  let projectIds = null;
19605
19654
  if (options2.projectId) {
19606
19655
  try {
@@ -19622,11 +19671,15 @@ async function searchObservations(options2) {
19622
19671
  }
19623
19672
  const hasQuery = options2.query && options2.query.trim().length > 0;
19624
19673
  const originalQuery = options2.query;
19625
- const tier = hasQuery ? classifyQueryTier(originalQuery) : "fast";
19674
+ const tier = quality === "fast" ? "fast" : hasQuery ? classifyQueryTier(originalQuery) : "fast";
19626
19675
  mark(`tier=${tier}`);
19627
- const expandedEmbeddingQuery = tier === "heavy" ? await maybeExpandSearchQuery(options2.query) : options2.query;
19676
+ if (quality === "thorough" && hasQuery) {
19677
+ const { initLLM: initLLM2, isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
19678
+ if (!isLLMEnabled2()) initLLM2();
19679
+ }
19680
+ const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options2.query) : options2.query;
19628
19681
  mark("queryExpansion");
19629
- const intentResult = hasQuery ? detectQueryIntent(originalQuery) : null;
19682
+ const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
19630
19683
  const requestLimit = projectIds ? (options2.limit ?? 20) * 3 : options2.limit ?? 20;
19631
19684
  const defaultBoost = {
19632
19685
  title: 3,
@@ -19640,17 +19693,21 @@ async function searchObservations(options2) {
19640
19693
  let searchParams = {
19641
19694
  term: originalQuery,
19642
19695
  limit: requestLimit,
19643
- includeVectors: true,
19696
+ // Fast search never uses vectors, so returning them only adds work and
19697
+ // payload pressure to an explicitly latency-sensitive path.
19698
+ includeVectors: quality !== "fast",
19644
19699
  ...Object.keys(filters).length > 0 ? { where: filters } : {},
19645
19700
  // Search specific fields (not tokens, accessCount, etc.)
19646
19701
  properties: ["title", "entityName", "narrative", "facts", "concepts", "filesModified"],
19647
19702
  // Field boosting: intent-aware or default
19648
19703
  boost: fieldBoost,
19649
19704
  // Fuzzy tolerance: allow 1-char typos for short queries, 2 for longer
19650
- ...hasQuery ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
19705
+ // Fuzzy matching is useful for ordinary recall, but grows sharply with
19706
+ // corpus size. The fast profile deliberately uses exact lexical matching.
19707
+ ...hasQuery && quality !== "fast" ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
19651
19708
  };
19652
19709
  let queryVector = null;
19653
- if (embeddingEnabled && hasQuery && tier !== "fast") {
19710
+ if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
19654
19711
  try {
19655
19712
  const provider2 = await getEmbeddingProvider();
19656
19713
  if (provider2) {
@@ -19665,11 +19722,11 @@ async function searchObservations(options2) {
19665
19722
  );
19666
19723
  } else {
19667
19724
  const EMBEDDING_TIMEOUT_MS = 15e3;
19668
- const embedPromise = provider2.embed(expandedEmbeddingQuery);
19669
- const timeoutPromise = new Promise(
19670
- (_4, reject) => setTimeout(() => reject(new Error(`Embedding timeout after ${EMBEDDING_TIMEOUT_MS}ms`)), EMBEDDING_TIMEOUT_MS)
19725
+ queryVector = await withTimeout(
19726
+ provider2.embed(expandedEmbeddingQuery),
19727
+ EMBEDDING_TIMEOUT_MS,
19728
+ "Embedding"
19671
19729
  );
19672
- queryVector = await Promise.race([embedPromise, timeoutPromise]);
19673
19730
  mark("embedding");
19674
19731
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
19675
19732
  const isCJKHeavy = cjkRatio > 0.3;
@@ -19910,7 +19967,7 @@ async function searchObservations(options2) {
19910
19967
  }
19911
19968
  }
19912
19969
  }
19913
- const shouldRerank = tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
19970
+ const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
19914
19971
  const top = intermediate[0]?.score ?? 0;
19915
19972
  const second2 = intermediate[1]?.score ?? 0;
19916
19973
  return top > 0 && second2 / top > 0.7;
@@ -19934,11 +19991,11 @@ async function searchObservations(options2) {
19934
19991
  }));
19935
19992
  const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
19936
19993
  const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
19937
- const rerankPromise = rerankResults2(originalQuery, candidates);
19938
- const timeoutPromise = new Promise(
19939
- (_4, reject) => setTimeout(() => reject(new Error(`LLM rerank timeout after ${RERANK_TIMEOUT_MS}ms`)), RERANK_TIMEOUT_MS)
19994
+ const { reranked, usedLLM } = await withTimeout(
19995
+ rerankResults2(originalQuery, candidates),
19996
+ RERANK_TIMEOUT_MS,
19997
+ "LLM rerank"
19940
19998
  );
19941
- const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
19942
19999
  mark(`rerank(usedLLM=${usedLLM})`);
19943
20000
  if (usedLLM) {
19944
20001
  lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
@@ -20131,6 +20188,7 @@ var init_orama_store = __esm({
20131
20188
  init_project_affinity();
20132
20189
  init_intent_detector();
20133
20190
  init_query_expansion();
20191
+ init_timeout();
20134
20192
  db = null;
20135
20193
  dbInitPromise = null;
20136
20194
  dbGeneration = 0;
@@ -23491,7 +23549,7 @@ var init_session_store = __esm({
23491
23549
  });
23492
23550
 
23493
23551
  // src/cli/commands/operator-shared.ts
23494
- async function getCliProjectContext(options2) {
23552
+ async function resolveCliProjectContext(options2) {
23495
23553
  const invocation = getCliInvocation();
23496
23554
  const detection = detectProjectWithDiagnostics(
23497
23555
  options2?.projectRoot ?? invocation.projectRoot ?? process.env.MEMORIX_PROJECT_ROOT ?? process.cwd()
@@ -23502,6 +23560,40 @@ async function getCliProjectContext(options2) {
23502
23560
  }
23503
23561
  const project = detection.project;
23504
23562
  const dataDir = await getProjectDataDir(project.id);
23563
+ return { project, dataDir, invocation };
23564
+ }
23565
+ async function getCliReadContext(options2) {
23566
+ const { project, dataDir, invocation } = await resolveCliProjectContext(options2);
23567
+ await initObservations(dataDir);
23568
+ if (options2?.searchIndex) {
23569
+ await prepareSearchIndex();
23570
+ }
23571
+ const storedIdentity = await loadCliIdentity(dataDir, project.id);
23572
+ if (!invocation.actorId && !storedIdentity) {
23573
+ return {
23574
+ project,
23575
+ dataDir,
23576
+ reader: { projectId: project.id },
23577
+ identity: null
23578
+ };
23579
+ }
23580
+ const teamStore = await initTeamStore(dataDir);
23581
+ const identity = await resolveCliIdentity({
23582
+ project,
23583
+ dataDir,
23584
+ teamStore,
23585
+ explicitActorId: invocation.actorId
23586
+ });
23587
+ return {
23588
+ project,
23589
+ dataDir,
23590
+ reader: identity.reader,
23591
+ identity: identity.identity,
23592
+ ...identity.warning ? { identityWarning: identity.warning } : {}
23593
+ };
23594
+ }
23595
+ async function getCliProjectContext(options2) {
23596
+ const { project, dataDir, invocation } = await resolveCliProjectContext(options2);
23505
23597
  try {
23506
23598
  const { MaintenanceTargetStore: MaintenanceTargetStore2 } = await Promise.resolve().then(() => (init_maintenance_targets(), maintenance_targets_exports));
23507
23599
  new MaintenanceTargetStore2(dataDir).register({
@@ -23583,6 +23675,13 @@ function coerceObservationStatus(input) {
23583
23675
  }
23584
23676
  return normalized;
23585
23677
  }
23678
+ function coerceRetrievalQuality(input) {
23679
+ const normalized = (input ?? "balanced").trim().toLowerCase();
23680
+ if (!RETRIEVAL_QUALITIES.includes(normalized)) {
23681
+ throw new Error("quality must be fast, balanced, or thorough");
23682
+ }
23683
+ return normalized;
23684
+ }
23586
23685
  function coerceObservationVisibility(input) {
23587
23686
  const normalized = (input ?? "project").trim().toLowerCase();
23588
23687
  if (normalized === "personal" || normalized === "project" || normalized === "team") {
@@ -23613,7 +23712,7 @@ function resolveCliActorId(explicitValue, identity, field = "agentId") {
23613
23712
  }
23614
23713
  return explicit || identity?.agentId;
23615
23714
  }
23616
- var OBSERVATION_TYPES, OBSERVATION_STATUSES;
23715
+ var OBSERVATION_TYPES, OBSERVATION_STATUSES, RETRIEVAL_QUALITIES;
23617
23716
  var init_operator_shared = __esm({
23618
23717
  "src/cli/commands/operator-shared.ts"() {
23619
23718
  "use strict";
@@ -23639,6 +23738,7 @@ var init_operator_shared = __esm({
23639
23738
  "probe"
23640
23739
  ];
23641
23740
  OBSERVATION_STATUSES = ["active", "resolved", "archived"];
23741
+ RETRIEVAL_QUALITIES = ["fast", "balanced", "thorough"];
23642
23742
  }
23643
23743
  });
23644
23744
 
@@ -24063,6 +24163,7 @@ var init_memory = __esm({
24063
24163
  topicKey: { type: "string", description: "Stable topic key override" },
24064
24164
  action: { type: "string", description: "Secondary action for advanced memory commands" },
24065
24165
  limit: { type: "string", description: "Limit for search/recent output" },
24166
+ quality: { type: "string", description: "Retrieval profile: fast, balanced (default), or thorough" },
24066
24167
  graphLimit: { type: "string", description: "Limit for graph-context output" },
24067
24168
  graphQuery: { type: "string", description: "Query for graph-context packet" },
24068
24169
  format: { type: "string", description: "Output format for graph-context: summary or prompt" },
@@ -24082,7 +24183,10 @@ var init_memory = __esm({
24082
24183
  const positional = (args._ ?? []).slice(1);
24083
24184
  const asJson = !!args.json;
24084
24185
  try {
24085
- const { project, dataDir, reader, identity } = await getCliProjectContext({ searchIndex: true });
24186
+ const readOnlyActions = /* @__PURE__ */ new Set(["", "search", "graph-context", "recent", "suggest-topic-key", "detail", "timeline"]);
24187
+ const needsSearchIndex = action === "search" || action === "detail" || action === "timeline";
24188
+ const context = readOnlyActions.has(action) ? await getCliReadContext({ searchIndex: needsSearchIndex }) : await getCliProjectContext({ searchIndex: true });
24189
+ const { project, dataDir, reader, identity } = context;
24086
24190
  switch (action) {
24087
24191
  case "search": {
24088
24192
  const query = getStringArg(args.query, positional);
@@ -24091,7 +24195,8 @@ var init_memory = __esm({
24091
24195
  return;
24092
24196
  }
24093
24197
  const limit = parsePositiveInt(args.limit, 10);
24094
- const result = await compactSearch({ query, limit, projectId: project.id, reader });
24198
+ const quality = coerceRetrievalQuality(args.quality);
24199
+ const result = await compactSearch({ query, limit, quality, projectId: project.id, reader });
24095
24200
  emitResult({ project, entries: result.entries }, result.formatted, asJson);
24096
24201
  return;
24097
24202
  }
@@ -24396,7 +24501,7 @@ var init_memory = __esm({
24396
24501
  console.log("Memorix Memory Commands");
24397
24502
  console.log("");
24398
24503
  console.log("Usage:");
24399
- console.log(' memorix memory search --query "timeout bug" [--limit 10]');
24504
+ console.log(' memorix memory search --query "timeout bug" [--limit 10] [--quality fast|balanced|thorough]');
24400
24505
  console.log(" memorix memory recent [--limit 10]");
24401
24506
  console.log(' memorix memory store --text "..." [--title "..."] [--type discovery] [--visibility project|personal|team]');
24402
24507
  console.log(' memorix memory suggest-topic-key --type decision --title "..."');
@@ -67802,21 +67907,6 @@ async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
67802
67907
  ]);
67803
67908
  return versioned ?? local;
67804
67909
  }
67805
- function withTimeout(promise, ms, label) {
67806
- return new Promise((resolve6, reject) => {
67807
- const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
67808
- promise.then(
67809
- (value) => {
67810
- clearTimeout(timer);
67811
- resolve6(value);
67812
- },
67813
- (error2) => {
67814
- clearTimeout(timer);
67815
- reject(error2);
67816
- }
67817
- );
67818
- });
67819
- }
67820
67910
  async function runAutomaticConsolidation(projectId, projectDir2, options2) {
67821
67911
  const { isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
67822
67912
  if (!isLLMEnabled2()) {
@@ -68201,6 +68291,7 @@ var init_project_maintenance = __esm({
68201
68291
  init_esm_shims();
68202
68292
  init_isolated_maintenance();
68203
68293
  init_lifecycle();
68294
+ init_timeout();
68204
68295
  DEFAULT_VECTOR_BATCH_SIZE = 12;
68205
68296
  DEFAULT_RETENTION_BATCH_SIZE = 100;
68206
68297
  DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
@@ -69960,15 +70051,6 @@ __export(server_exports2, {
69960
70051
  shouldAwaitProjectRuntime: () => shouldAwaitProjectRuntime
69961
70052
  });
69962
70053
  import { createHash as createHash14 } from "crypto";
69963
- function withTimeout2(promise, ms, label) {
69964
- let timer;
69965
- return Promise.race([
69966
- promise,
69967
- new Promise((_4, reject) => {
69968
- timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
69969
- })
69970
- ]).finally(() => clearTimeout(timer));
69971
- }
69972
70054
  function formatFormationStageDurations(stageDurationsMs) {
69973
70055
  const orderedStages = ["extract", "resolve", "evaluate"];
69974
70056
  const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
@@ -70323,7 +70405,7 @@ The path should point to a directory containing a .git folder.`
70323
70405
  };
70324
70406
  const server = existingServer ?? new McpServer({
70325
70407
  name: "memorix",
70326
- version: true ? "1.2.4" : "1.0.1"
70408
+ version: true ? "1.2.5" : "1.0.1"
70327
70409
  });
70328
70410
  const originalRegisterTool = server.registerTool.bind(server);
70329
70411
  server.registerTool = ((name, ...args) => {
@@ -70520,7 +70602,7 @@ The path should point to a directory containing a .git folder.`
70520
70602
  getEntityNames: () => graphManager.getEntityNames(),
70521
70603
  onStageEvent: onFormationStageEvent
70522
70604
  };
70523
- formationResult = await withTimeout2(
70605
+ formationResult = await withTimeout(
70524
70606
  runFormation({
70525
70607
  entityName,
70526
70608
  type,
@@ -70641,7 +70723,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
70641
70723
  facts: d3.facts,
70642
70724
  score: similarEntries[i2]?.score ?? 0
70643
70725
  }));
70644
- const decision = await withTimeout2(
70726
+ const decision = await withTimeout(
70645
70727
  compactOnWrite(
70646
70728
  { title, narrative, facts: safeFacts ?? [] },
70647
70729
  existingMemories
@@ -70737,7 +70819,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
70737
70819
  let compressionNote = "";
70738
70820
  try {
70739
70821
  const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
70740
- const { compressed, saved, usedLLM } = await withTimeout2(
70822
+ const { compressed, saved, usedLLM } = await withTimeout(
70741
70823
  compressNarrative2(narrative, safeFacts, type),
70742
70824
  COMPRESSION_TIMEOUT_MS,
70743
70825
  "Narrative compression"
@@ -70859,7 +70941,7 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
70859
70941
  },
70860
70942
  getEntityNames: () => graphManager.getEntityNames()
70861
70943
  };
70862
- const formed = await withTimeout2(
70944
+ const formed = await withTimeout(
70863
70945
  runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
70864
70946
  FORMATION_TIMEOUT_MS,
70865
70947
  "Shadow formation"
@@ -70950,6 +71032,9 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
70950
71032
  source: external_exports.enum(["agent", "git", "manual"]).optional().describe(
70951
71033
  'Filter by memory source. "git" returns only commit-derived ground truth memories. Omit for all sources.'
70952
71034
  ),
71035
+ quality: external_exports.enum(["fast", "balanced", "thorough"]).optional().default("balanced").describe(
71036
+ "Retrieval profile: fast stays local, balanced uses configured embeddings, thorough explicitly permits optional LLM refinement."
71037
+ ),
70953
71038
  purpose: external_exports.string().optional().describe(
70954
71039
  "Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
70955
71040
  ),
@@ -70958,7 +71043,7 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
70958
71043
  )
70959
71044
  }
70960
71045
  },
70961
- async ({ query, limit, type, maxTokens, scope, since, until, status, source, purpose, force }) => {
71046
+ async ({ query, limit, type, maxTokens, scope, since, until, status, source, quality, purpose, force }) => {
70962
71047
  if (scope !== "global") {
70963
71048
  const unresolved = requireResolvedProject("search the current project");
70964
71049
  if (unresolved) return unresolved;
@@ -70983,16 +71068,14 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
70983
71068
  projectId: scope === "global" ? void 0 : project.id,
70984
71069
  status: status ?? "active",
70985
71070
  source,
71071
+ quality,
70986
71072
  reader: getObservationReader(scope === "global" ? "global" : "project")
70987
71073
  });
70988
- const timeoutPromise = new Promise(
70989
- (_4, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
70990
- );
70991
71074
  let result;
70992
71075
  try {
70993
- result = await Promise.race([searchPromise, timeoutPromise]);
71076
+ result = await withTimeout(searchPromise, TIMEOUT_MS, "Search");
70994
71077
  } catch (error2) {
70995
- if (error2 instanceof Error && error2.message.includes("timeout")) {
71078
+ if (error2 instanceof Error && /\btimeout\b|timed out/i.test(error2.message)) {
70996
71079
  return {
70997
71080
  content: [
70998
71081
  {
@@ -74085,6 +74168,7 @@ var init_server4 = __esm({
74085
74168
  init_memory_manager();
74086
74169
  init_formation();
74087
74170
  init_formation_timeout();
74171
+ init_timeout();
74088
74172
  FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
74089
74173
  COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
74090
74174
  COMPRESSION_TIMEOUT_MS = 5e3;
@@ -91533,12 +91617,14 @@ var main = defineCommand({
91533
91617
  args: {
91534
91618
  query: { type: "positional", description: "Search query", required: true },
91535
91619
  limit: { type: "string", description: "Maximum results" },
91620
+ quality: { type: "string", description: "Retrieval profile: fast, balanced (default), or thorough" },
91536
91621
  json: { type: "boolean", description: "Emit machine-readable JSON output" }
91537
91622
  },
91538
91623
  async run({ args }) {
91539
91624
  await runMemoryShortcut("search", {
91540
91625
  query: args.query,
91541
91626
  limit: args.limit,
91627
+ quality: args.quality,
91542
91628
  json: args.json
91543
91629
  });
91544
91630
  }