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 +13 -0
- package/dist/cli/index.js +147 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +92 -55
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +79 -35
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +13 -0
- package/dist/sdk.d.ts +4 -2
- package/dist/sdk.js +93 -55
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.5-RETRIEVAL-PERFORMANCE.md +85 -0
- package/docs/PERFORMANCE.md +22 -1
- package/docs/dev-log/progress.txt +33 -8
- package/package.json +2 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/operator-shared.ts +74 -2
- package/src/cli/index.ts +2 -0
- package/src/config/resolved-config.ts +29 -4
- package/src/runtime/project-maintenance.ts +1 -14
- package/src/sdk.ts +4 -0
- package/src/server.ts +8 -18
- package/src/store/orama-store.ts +39 -18
- package/src/timeout.ts +23 -0
- package/src/types.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -2803,6 +2803,25 @@ function getResolvedConfig(options = {}) {
|
|
|
2803
2803
|
const legacy = loadFileConfig();
|
|
2804
2804
|
const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml2.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
|
|
2805
2805
|
const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
2806
|
+
const memoryLlmProvider = first(
|
|
2807
|
+
process.env.MEMORIX_LLM_PROVIDER,
|
|
2808
|
+
toml.memory?.llm?.provider,
|
|
2809
|
+
yaml2.llm?.provider,
|
|
2810
|
+
legacy.llm?.provider
|
|
2811
|
+
);
|
|
2812
|
+
const memoryLlmModel = first(
|
|
2813
|
+
process.env.MEMORIX_LLM_MODEL,
|
|
2814
|
+
toml.memory?.llm?.model,
|
|
2815
|
+
yaml2.llm?.model,
|
|
2816
|
+
legacy.llm?.model
|
|
2817
|
+
);
|
|
2818
|
+
const memoryLlmBaseUrl = first(
|
|
2819
|
+
process.env.MEMORIX_LLM_BASE_URL,
|
|
2820
|
+
toml.memory?.llm?.base_url,
|
|
2821
|
+
yaml2.llm?.baseUrl,
|
|
2822
|
+
legacy.llm?.baseUrl
|
|
2823
|
+
);
|
|
2824
|
+
const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
2806
2825
|
const resolved = {
|
|
2807
2826
|
agent: {
|
|
2808
2827
|
provider: first(
|
|
@@ -2840,9 +2859,9 @@ function getResolvedConfig(options = {}) {
|
|
|
2840
2859
|
autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml2.behavior?.autoCleanup),
|
|
2841
2860
|
syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml2.behavior?.syncAdvisory),
|
|
2842
2861
|
llm: {
|
|
2843
|
-
provider:
|
|
2844
|
-
model:
|
|
2845
|
-
baseUrl:
|
|
2862
|
+
provider: memoryLlmProvider,
|
|
2863
|
+
model: memoryLlmModel,
|
|
2864
|
+
baseUrl: memoryLlmBaseUrl,
|
|
2846
2865
|
apiKey: first(
|
|
2847
2866
|
process.env.MEMORIX_LLM_API_KEY,
|
|
2848
2867
|
process.env.MEMORIX_API_KEY,
|
|
@@ -2851,7 +2870,7 @@ function getResolvedConfig(options = {}) {
|
|
|
2851
2870
|
legacy.llm?.apiKey,
|
|
2852
2871
|
process.env.OPENAI_API_KEY,
|
|
2853
2872
|
process.env.ANTHROPIC_API_KEY,
|
|
2854
|
-
|
|
2873
|
+
openRouterMemoryLlmApiKey
|
|
2855
2874
|
)
|
|
2856
2875
|
}
|
|
2857
2876
|
},
|
|
@@ -2988,6 +3007,9 @@ function isOpenRouterUrl(value) {
|
|
|
2988
3007
|
return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
|
|
2989
3008
|
}
|
|
2990
3009
|
}
|
|
3010
|
+
function isOpenRouterMemoryLane(provider2, baseUrl) {
|
|
3011
|
+
return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
|
|
3012
|
+
}
|
|
2991
3013
|
function normalizeExternalContext(value) {
|
|
2992
3014
|
const normalized = value?.trim().toLowerCase();
|
|
2993
3015
|
if (normalized === "auto" || normalized === "off") return normalized;
|
|
@@ -5035,6 +5057,29 @@ Rules:
|
|
|
5035
5057
|
}
|
|
5036
5058
|
});
|
|
5037
5059
|
|
|
5060
|
+
// src/timeout.ts
|
|
5061
|
+
function withTimeout(promise, ms, label) {
|
|
5062
|
+
return new Promise((resolve2, reject) => {
|
|
5063
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
5064
|
+
promise.then(
|
|
5065
|
+
(value) => {
|
|
5066
|
+
clearTimeout(timer);
|
|
5067
|
+
resolve2(value);
|
|
5068
|
+
},
|
|
5069
|
+
(error) => {
|
|
5070
|
+
clearTimeout(timer);
|
|
5071
|
+
reject(error);
|
|
5072
|
+
}
|
|
5073
|
+
);
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
var init_timeout = __esm({
|
|
5077
|
+
"src/timeout.ts"() {
|
|
5078
|
+
"use strict";
|
|
5079
|
+
init_esm_shims();
|
|
5080
|
+
}
|
|
5081
|
+
});
|
|
5082
|
+
|
|
5038
5083
|
// src/project/aliases.ts
|
|
5039
5084
|
var aliases_exports = {};
|
|
5040
5085
|
__export(aliases_exports, {
|
|
@@ -5800,8 +5845,12 @@ async function searchObservations(options) {
|
|
|
5800
5845
|
}
|
|
5801
5846
|
};
|
|
5802
5847
|
const modeKey = options.projectId ?? SEARCH_MODE_DEFAULT_KEY;
|
|
5803
|
-
|
|
5848
|
+
const quality = options.quality ?? "balanced";
|
|
5804
5849
|
const database = await getDb();
|
|
5850
|
+
lastSearchModeByProject.set(
|
|
5851
|
+
modeKey,
|
|
5852
|
+
quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
|
|
5853
|
+
);
|
|
5805
5854
|
let projectIds = null;
|
|
5806
5855
|
if (options.projectId) {
|
|
5807
5856
|
try {
|
|
@@ -5823,11 +5872,15 @@ async function searchObservations(options) {
|
|
|
5823
5872
|
}
|
|
5824
5873
|
const hasQuery = options.query && options.query.trim().length > 0;
|
|
5825
5874
|
const originalQuery = options.query;
|
|
5826
|
-
const tier = hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5875
|
+
const tier = quality === "fast" ? "fast" : hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5827
5876
|
mark(`tier=${tier}`);
|
|
5828
|
-
|
|
5877
|
+
if (quality === "thorough" && hasQuery) {
|
|
5878
|
+
const { initLLM: initLLM2, isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
5879
|
+
if (!isLLMEnabled2()) initLLM2();
|
|
5880
|
+
}
|
|
5881
|
+
const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
|
|
5829
5882
|
mark("queryExpansion");
|
|
5830
|
-
const intentResult = hasQuery ? detectQueryIntent(originalQuery)
|
|
5883
|
+
const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
|
|
5831
5884
|
const requestLimit = projectIds ? (options.limit ?? 20) * 3 : options.limit ?? 20;
|
|
5832
5885
|
const defaultBoost = {
|
|
5833
5886
|
title: 3,
|
|
@@ -5841,17 +5894,21 @@ async function searchObservations(options) {
|
|
|
5841
5894
|
let searchParams = {
|
|
5842
5895
|
term: originalQuery,
|
|
5843
5896
|
limit: requestLimit,
|
|
5844
|
-
|
|
5897
|
+
// Fast search never uses vectors, so returning them only adds work and
|
|
5898
|
+
// payload pressure to an explicitly latency-sensitive path.
|
|
5899
|
+
includeVectors: quality !== "fast",
|
|
5845
5900
|
...Object.keys(filters).length > 0 ? { where: filters } : {},
|
|
5846
5901
|
// Search specific fields (not tokens, accessCount, etc.)
|
|
5847
5902
|
properties: ["title", "entityName", "narrative", "facts", "concepts", "filesModified"],
|
|
5848
5903
|
// Field boosting: intent-aware or default
|
|
5849
5904
|
boost: fieldBoost,
|
|
5850
5905
|
// Fuzzy tolerance: allow 1-char typos for short queries, 2 for longer
|
|
5851
|
-
|
|
5906
|
+
// Fuzzy matching is useful for ordinary recall, but grows sharply with
|
|
5907
|
+
// corpus size. The fast profile deliberately uses exact lexical matching.
|
|
5908
|
+
...hasQuery && quality !== "fast" ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
|
|
5852
5909
|
};
|
|
5853
5910
|
let queryVector = null;
|
|
5854
|
-
if (embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5911
|
+
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5855
5912
|
try {
|
|
5856
5913
|
const provider2 = await getEmbeddingProvider();
|
|
5857
5914
|
if (provider2) {
|
|
@@ -5866,11 +5923,11 @@ async function searchObservations(options) {
|
|
|
5866
5923
|
);
|
|
5867
5924
|
} else {
|
|
5868
5925
|
const EMBEDDING_TIMEOUT_MS = 15e3;
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5926
|
+
queryVector = await withTimeout(
|
|
5927
|
+
provider2.embed(expandedEmbeddingQuery),
|
|
5928
|
+
EMBEDDING_TIMEOUT_MS,
|
|
5929
|
+
"Embedding"
|
|
5872
5930
|
);
|
|
5873
|
-
queryVector = await Promise.race([embedPromise, timeoutPromise]);
|
|
5874
5931
|
mark("embedding");
|
|
5875
5932
|
const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
|
|
5876
5933
|
const isCJKHeavy = cjkRatio > 0.3;
|
|
@@ -6111,7 +6168,7 @@ async function searchObservations(options) {
|
|
|
6111
6168
|
}
|
|
6112
6169
|
}
|
|
6113
6170
|
}
|
|
6114
|
-
const shouldRerank = tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
6171
|
+
const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
6115
6172
|
const top = intermediate[0]?.score ?? 0;
|
|
6116
6173
|
const second = intermediate[1]?.score ?? 0;
|
|
6117
6174
|
return top > 0 && second / top > 0.7;
|
|
@@ -6135,11 +6192,11 @@ async function searchObservations(options) {
|
|
|
6135
6192
|
}));
|
|
6136
6193
|
const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
|
|
6137
6194
|
const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
|
|
6138
|
-
const
|
|
6139
|
-
|
|
6140
|
-
|
|
6195
|
+
const { reranked, usedLLM } = await withTimeout(
|
|
6196
|
+
rerankResults2(originalQuery, candidates),
|
|
6197
|
+
RERANK_TIMEOUT_MS,
|
|
6198
|
+
"LLM rerank"
|
|
6141
6199
|
);
|
|
6142
|
-
const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
|
|
6143
6200
|
mark(`rerank(usedLLM=${usedLLM})`);
|
|
6144
6201
|
if (usedLLM) {
|
|
6145
6202
|
lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
|
|
@@ -6331,6 +6388,7 @@ var init_orama_store = __esm({
|
|
|
6331
6388
|
init_project_affinity();
|
|
6332
6389
|
init_intent_detector();
|
|
6333
6390
|
init_query_expansion();
|
|
6391
|
+
init_timeout();
|
|
6334
6392
|
db = null;
|
|
6335
6393
|
dbInitPromise = null;
|
|
6336
6394
|
dbGeneration = 0;
|
|
@@ -14855,21 +14913,6 @@ async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
|
|
|
14855
14913
|
]);
|
|
14856
14914
|
return versioned ?? local;
|
|
14857
14915
|
}
|
|
14858
|
-
function withTimeout(promise, ms, label) {
|
|
14859
|
-
return new Promise((resolve2, reject) => {
|
|
14860
|
-
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
14861
|
-
promise.then(
|
|
14862
|
-
(value) => {
|
|
14863
|
-
clearTimeout(timer);
|
|
14864
|
-
resolve2(value);
|
|
14865
|
-
},
|
|
14866
|
-
(error) => {
|
|
14867
|
-
clearTimeout(timer);
|
|
14868
|
-
reject(error);
|
|
14869
|
-
}
|
|
14870
|
-
);
|
|
14871
|
-
});
|
|
14872
|
-
}
|
|
14873
14916
|
async function runAutomaticConsolidation(projectId, projectDir2, options) {
|
|
14874
14917
|
const { isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider2(), provider_exports2));
|
|
14875
14918
|
if (!isLLMEnabled2()) {
|
|
@@ -15254,6 +15297,7 @@ var init_project_maintenance = __esm({
|
|
|
15254
15297
|
init_esm_shims();
|
|
15255
15298
|
init_isolated_maintenance();
|
|
15256
15299
|
init_lifecycle();
|
|
15300
|
+
init_timeout();
|
|
15257
15301
|
DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
15258
15302
|
DEFAULT_RETENTION_BATCH_SIZE = 100;
|
|
15259
15303
|
DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
|
|
@@ -26889,18 +26933,10 @@ function parseFormationTimeoutMs(raw) {
|
|
|
26889
26933
|
}
|
|
26890
26934
|
|
|
26891
26935
|
// src/server.ts
|
|
26936
|
+
init_timeout();
|
|
26892
26937
|
var FORMATION_TIMEOUT_MS = parseFormationTimeoutMs(process.env.MEMORIX_FORMATION_TIMEOUT_MS);
|
|
26893
26938
|
var COMPACT_ON_WRITE_TIMEOUT_MS = 12e3;
|
|
26894
26939
|
var COMPRESSION_TIMEOUT_MS = 5e3;
|
|
26895
|
-
function withTimeout2(promise, ms, label) {
|
|
26896
|
-
let timer;
|
|
26897
|
-
return Promise.race([
|
|
26898
|
-
promise,
|
|
26899
|
-
new Promise((_, reject) => {
|
|
26900
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
26901
|
-
})
|
|
26902
|
-
]).finally(() => clearTimeout(timer));
|
|
26903
|
-
}
|
|
26904
26940
|
function formatFormationStageDurations(stageDurationsMs) {
|
|
26905
26941
|
const orderedStages = ["extract", "resolve", "evaluate"];
|
|
26906
26942
|
const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
|
|
@@ -27276,7 +27312,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
27276
27312
|
};
|
|
27277
27313
|
const server = existingServer ?? new McpServer({
|
|
27278
27314
|
name: "memorix",
|
|
27279
|
-
version: true ? "1.2.
|
|
27315
|
+
version: true ? "1.2.5" : "1.0.1"
|
|
27280
27316
|
});
|
|
27281
27317
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
27282
27318
|
server.registerTool = ((name, ...args) => {
|
|
@@ -27473,7 +27509,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
27473
27509
|
getEntityNames: () => graphManager.getEntityNames(),
|
|
27474
27510
|
onStageEvent: onFormationStageEvent
|
|
27475
27511
|
};
|
|
27476
|
-
formationResult = await
|
|
27512
|
+
formationResult = await withTimeout(
|
|
27477
27513
|
runFormation({
|
|
27478
27514
|
entityName,
|
|
27479
27515
|
type,
|
|
@@ -27594,7 +27630,7 @@ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formati
|
|
|
27594
27630
|
facts: d.facts,
|
|
27595
27631
|
score: similarEntries[i]?.score ?? 0
|
|
27596
27632
|
}));
|
|
27597
|
-
const decision = await
|
|
27633
|
+
const decision = await withTimeout(
|
|
27598
27634
|
compactOnWrite(
|
|
27599
27635
|
{ title, narrative, facts: safeFacts ?? [] },
|
|
27600
27636
|
existingMemories
|
|
@@ -27690,7 +27726,7 @@ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
|
|
|
27690
27726
|
let compressionNote = "";
|
|
27691
27727
|
try {
|
|
27692
27728
|
const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
|
|
27693
|
-
const { compressed, saved, usedLLM } = await
|
|
27729
|
+
const { compressed, saved, usedLLM } = await withTimeout(
|
|
27694
27730
|
compressNarrative2(narrative, safeFacts, type),
|
|
27695
27731
|
COMPRESSION_TIMEOUT_MS,
|
|
27696
27732
|
"Narrative compression"
|
|
@@ -27812,7 +27848,7 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
|
|
|
27812
27848
|
},
|
|
27813
27849
|
getEntityNames: () => graphManager.getEntityNames()
|
|
27814
27850
|
};
|
|
27815
|
-
const formed = await
|
|
27851
|
+
const formed = await withTimeout(
|
|
27816
27852
|
runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
|
|
27817
27853
|
FORMATION_TIMEOUT_MS,
|
|
27818
27854
|
"Shadow formation"
|
|
@@ -27903,6 +27939,9 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27903
27939
|
source: z2.enum(["agent", "git", "manual"]).optional().describe(
|
|
27904
27940
|
'Filter by memory source. "git" returns only commit-derived ground truth memories. Omit for all sources.'
|
|
27905
27941
|
),
|
|
27942
|
+
quality: z2.enum(["fast", "balanced", "thorough"]).optional().default("balanced").describe(
|
|
27943
|
+
"Retrieval profile: fast stays local, balanced uses configured embeddings, thorough explicitly permits optional LLM refinement."
|
|
27944
|
+
),
|
|
27906
27945
|
purpose: z2.string().optional().describe(
|
|
27907
27946
|
"Why this must expand beyond the latest Autopilot brief. Name the missing fact or the user's explicit request."
|
|
27908
27947
|
),
|
|
@@ -27911,7 +27950,7 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27911
27950
|
)
|
|
27912
27951
|
}
|
|
27913
27952
|
},
|
|
27914
|
-
async ({ query, limit, type, maxTokens, scope, since, until, status, source, purpose, force }) => {
|
|
27953
|
+
async ({ query, limit, type, maxTokens, scope, since, until, status, source, quality, purpose, force }) => {
|
|
27915
27954
|
if (scope !== "global") {
|
|
27916
27955
|
const unresolved = requireResolvedProject("search the current project");
|
|
27917
27956
|
if (unresolved) return unresolved;
|
|
@@ -27936,16 +27975,14 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
27936
27975
|
projectId: scope === "global" ? void 0 : project.id,
|
|
27937
27976
|
status: status ?? "active",
|
|
27938
27977
|
source,
|
|
27978
|
+
quality,
|
|
27939
27979
|
reader: getObservationReader(scope === "global" ? "global" : "project")
|
|
27940
27980
|
});
|
|
27941
|
-
const timeoutPromise = new Promise(
|
|
27942
|
-
(_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
|
|
27943
|
-
);
|
|
27944
27981
|
let result;
|
|
27945
27982
|
try {
|
|
27946
|
-
result = await
|
|
27983
|
+
result = await withTimeout(searchPromise, TIMEOUT_MS, "Search");
|
|
27947
27984
|
} catch (error) {
|
|
27948
|
-
if (error instanceof Error && error.message
|
|
27985
|
+
if (error instanceof Error && /\btimeout\b|timed out/i.test(error.message)) {
|
|
27949
27986
|
return {
|
|
27950
27987
|
content: [
|
|
27951
27988
|
{
|