@melandlabs/memory-store 1.1.8 → 1.2.1

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
2
+ import { isFactType, vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
3
3
  import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
4
4
  import { ChromaVectorStore } from '@melandlabs/rag';
5
5
  import { mkdirSync } from 'fs';
@@ -10,6 +10,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
11
  import { z } from 'zod';
12
12
  import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidation';
13
+ import { isDerivedKind } from '@melandlabs/contracts/derived-fact';
14
+ import { isEntityKind } from '@melandlabs/contracts/entity-edge';
13
15
  import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
14
16
 
15
17
  var __defProp = Object.defineProperty;
@@ -412,6 +414,9 @@ async function applyReranker(reranker, query, results) {
412
414
  }
413
415
 
414
416
  // src/search/utilities.ts
417
+ function deriveLexicalKeywords(query) {
418
+ return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
419
+ }
415
420
  var DEFAULT_LIMIT = 10;
416
421
  var DEFAULT_THRESHOLD = 0.7;
417
422
  var DEFAULT_SOURCES = ["memory", "insights", "knowledge"];
@@ -525,6 +530,50 @@ function toMemoryResult(result) {
525
530
  metadata: result.metadata
526
531
  };
527
532
  }
533
+ function materializeSignals(lists, hit) {
534
+ const channels = [];
535
+ let semantic;
536
+ let lexical;
537
+ let entity;
538
+ for (const list of lists) {
539
+ const channel = listNameToChannel(list.name);
540
+ if (!channel) continue;
541
+ if (channels.includes(channel)) continue;
542
+ let found;
543
+ for (const candidate of list.hits) {
544
+ if (candidate.type === hit.type && candidate.id === hit.id) {
545
+ found = candidate;
546
+ break;
547
+ }
548
+ }
549
+ if (!found) continue;
550
+ channels.push(channel);
551
+ if (channel === "semantic" && semantic === void 0) {
552
+ semantic = found.similarity;
553
+ } else if (channel === "lexical" && lexical === void 0) {
554
+ lexical = found.similarity;
555
+ } else if (channel === "entity" && entity === void 0) {
556
+ entity = found.similarity;
557
+ }
558
+ }
559
+ if (channels.length === 0) {
560
+ return void 0;
561
+ }
562
+ const out = { channels };
563
+ if (semantic !== void 0) out.semantic = semantic;
564
+ if (lexical !== void 0) out.lexical = lexical;
565
+ if (entity !== void 0) out.entity = entity;
566
+ if (typeof hit.metadata.rrfScore === "number") {
567
+ out.rrf = hit.metadata.rrfScore;
568
+ }
569
+ return out;
570
+ }
571
+ function listNameToChannel(name) {
572
+ if (name === "memory-semantic") return "semantic";
573
+ if (name === "memory-bm25" || name === "memory-lexical") return "lexical";
574
+ if (name === "memory-entity") return "entity";
575
+ return void 0;
576
+ }
528
577
  function isRawMemorySemanticResult(result) {
529
578
  if (!result || typeof result !== "object") {
530
579
  return false;
@@ -792,8 +841,65 @@ async function resolveSearchScopePeers(input) {
792
841
  }
793
842
 
794
843
  // src/search/unified-search.ts
795
- function deriveLexicalKeywords(query) {
796
- return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
844
+ function deriveEntityKeywords(query) {
845
+ const proper = Array.from(new Set(query.match(/\b[A-Z][a-zA-Z]{2,}\b/g) ?? [])).map((w) => w.toLowerCase());
846
+ const lexical = deriveLexicalKeywords(query);
847
+ const seen = /* @__PURE__ */ new Set();
848
+ const out = [];
849
+ for (const word of [...proper, ...lexical]) {
850
+ if (word.length < 2) continue;
851
+ if (seen.has(word)) continue;
852
+ seen.add(word);
853
+ out.push(word);
854
+ }
855
+ return out.slice(0, 16);
856
+ }
857
+ async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = []) {
858
+ if (typeof deps.entitySearch !== "function") {
859
+ return [];
860
+ }
861
+ const keywords = deriveEntityKeywords(input.query);
862
+ if (keywords.length === 0) {
863
+ return [];
864
+ }
865
+ try {
866
+ const entitySearch = deps.entitySearch;
867
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
868
+ const matches = (await Promise.all(
869
+ filters.map(
870
+ (botId) => entitySearch({
871
+ userId: input.userId,
872
+ keywords,
873
+ limit: Math.ceil(limit / filters.length),
874
+ botId,
875
+ ...peerPeers.length > 0 ? { peers: peerPeers } : {}
876
+ })
877
+ )
878
+ )).flat();
879
+ return matches.filter(
880
+ (m) => typeof m?.messageId === "string" && typeof m?.label === "string" && typeof m?.score === "number"
881
+ ).map((m) => ({
882
+ type: "memory",
883
+ id: m.messageId,
884
+ // The host's `entitySearch` returns match ids + labels only,
885
+ // so we surface the entity label as `content`. When the same
886
+ // `messageId` is also returned by the semantic/lexical channel
887
+ // (RRF path), the real message content is preserved via the
888
+ // shared `(type, id)` key; otherwise callers can re-fetch the
889
+ // original message by `id` (set in metadata as `sourceMessageId`).
890
+ content: m.label,
891
+ similarity: m.score,
892
+ metadata: {
893
+ scoring: "entity",
894
+ entityLabel: m.label,
895
+ isEntityProjection: true,
896
+ sourceMessageId: m.messageId
897
+ }
898
+ }));
899
+ } catch (error) {
900
+ logger.warn?.("[memory-store] entity search failed:", error);
901
+ return [];
902
+ }
797
903
  }
798
904
  function parseDateBoundary(value, role) {
799
905
  const trimmed = value.trim();
@@ -1233,10 +1339,39 @@ function createUnifiedSearch(deps = {}) {
1233
1339
  });
1234
1340
  }
1235
1341
  }
1236
- return {
1342
+ let entity;
1343
+ if (typeof deps.entitySearch === "function") {
1344
+ entity = filterByDateRange(
1345
+ await runEntitySearchForQuery(deps, input, limit, logger, peerPeers),
1346
+ input.dateFrom,
1347
+ input.dateTo
1348
+ );
1349
+ if (entity.length === 0) {
1350
+ entity = void 0;
1351
+ } else if (normalizeUnifiedMemoryMergeStrategy(
1352
+ input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1353
+ ) !== "rrf") {
1354
+ warnings.push({
1355
+ source: "memory",
1356
+ code: "memory_entity_requires_rrf",
1357
+ message: "Entity channel results are fused via RRF; with mergeStrategy:'similarity' standalone entity hits are suppressed (only the entity channel signal is kept on co-occurring hits). Request mergeStrategy:'rrf' to surface entity matches."
1358
+ });
1359
+ }
1360
+ } else if (input.mergeStrategy === "rrf") {
1361
+ warnings.push({
1362
+ source: "memory",
1363
+ code: "memory_entity_search_not_configured",
1364
+ message: "RRF merge requested but entity search is not configured; falling back to semantic+lexical RRF."
1365
+ });
1366
+ }
1367
+ const out = {
1237
1368
  semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
1238
1369
  lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
1239
1370
  };
1371
+ if (entity && entity.length > 0) {
1372
+ out.entity = entity;
1373
+ }
1374
+ return out;
1240
1375
  }
1241
1376
  async function searchUnifiedMemory(input) {
1242
1377
  const query = input.query.trim();
@@ -1507,32 +1642,56 @@ function createUnifiedSearch(deps = {}) {
1507
1642
  }
1508
1643
  function mergeAcrossSources(input) {
1509
1644
  const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
1645
+ const lists = buildChannelLists(memorySubs, input.insightHits, input.knowledgeHits);
1646
+ if (input.strategy !== "rrf") {
1647
+ const seen = /* @__PURE__ */ new Map();
1648
+ for (const hit of [
1649
+ ...memorySubs.semantic,
1650
+ ...memorySubs.lexical,
1651
+ ...input.insightHits,
1652
+ ...input.knowledgeHits
1653
+ ]) {
1654
+ const key = `${hit.type}::${hit.id}`;
1655
+ const existing = seen.get(key);
1656
+ if (!existing || hit.similarity > existing.similarity) {
1657
+ seen.set(key, hit);
1658
+ }
1659
+ }
1660
+ const merged2 = mergeUnifiedMemorySearchResults(Array.from(seen.values()), input.limit);
1661
+ return attachSignals(merged2, lists);
1662
+ }
1510
1663
  const all = [
1511
1664
  ...memorySubs.semantic,
1512
1665
  ...memorySubs.lexical,
1666
+ ...memorySubs.entity ?? [],
1513
1667
  ...input.insightHits,
1514
1668
  ...input.knowledgeHits
1515
1669
  ];
1516
- if (input.strategy !== "rrf") {
1517
- return mergeUnifiedMemorySearchResults(all, input.limit);
1518
- }
1670
+ const merged = lists.length <= 1 ? mergeUnifiedMemorySearchResults(all, input.limit) : mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
1671
+ return attachSignals(merged, lists);
1672
+ }
1673
+ function buildChannelLists(memorySubs, insightHits, knowledgeHits) {
1519
1674
  const lists = [];
1520
- if (memorySubs.semantic.length > 0) {
1521
- lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
1522
- }
1523
- if (memorySubs.lexical.length > 0) {
1524
- lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
1525
- }
1526
- if (input.insightHits.length > 0) {
1527
- lists.push({ name: "insights", hits: input.insightHits });
1528
- }
1529
- if (input.knowledgeHits.length > 0) {
1530
- lists.push({ name: "knowledge", hits: input.knowledgeHits });
1675
+ if (memorySubs.semantic.length > 0) lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
1676
+ if (memorySubs.lexical.length > 0) lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
1677
+ if (memorySubs.entity && memorySubs.entity.length > 0) {
1678
+ lists.push({ name: "memory-entity", hits: memorySubs.entity });
1531
1679
  }
1532
- if (lists.length <= 1) {
1533
- return mergeUnifiedMemorySearchResults(all, input.limit);
1680
+ if (insightHits.length > 0) lists.push({ name: "insights", hits: insightHits });
1681
+ if (knowledgeHits.length > 0) lists.push({ name: "knowledge", hits: knowledgeHits });
1682
+ return lists;
1683
+ }
1684
+ function attachSignals(hits, lists) {
1685
+ if (hits.length === 0 || lists.length === 0) {
1686
+ return hits;
1534
1687
  }
1535
- return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
1688
+ return hits.map((hit) => {
1689
+ const signals = materializeSignals(lists, hit);
1690
+ if (!signals) {
1691
+ return hit;
1692
+ }
1693
+ return { ...hit, signals };
1694
+ });
1536
1695
  }
1537
1696
 
1538
1697
  // src/search/apply-reflect.ts
@@ -1651,6 +1810,177 @@ function makePlanSummary(plan) {
1651
1810
  }
1652
1811
  return lines.join("\n");
1653
1812
  }
1813
+ var DEFAULT_CANDIDATE_LIMIT = 50;
1814
+ async function deriveFacts(deps, input, logger = console) {
1815
+ const warnings = [];
1816
+ if (typeof deps.deriver !== "function") {
1817
+ warnings.push({
1818
+ code: "derive_deriver_not_configured",
1819
+ message: "No `deriver` is wired into the unified search deps; returning an empty fact list."
1820
+ });
1821
+ return { facts: [], warnings };
1822
+ }
1823
+ const limit = Math.max(1, Math.min(500, input.candidateLimit ?? DEFAULT_CANDIDATE_LIMIT));
1824
+ if (!input.candidateTexts || input.candidateTexts.length === 0) {
1825
+ if (!input.query) {
1826
+ warnings.push({
1827
+ code: "derive_fallback_query_noise",
1828
+ message: "No `query` supplied and no `candidateTexts`; lexical fallback derives keywords from `userId + botIds`, which is rarely useful. Pass `query` (or `candidateTexts`) for topical derivation."
1829
+ });
1830
+ }
1831
+ }
1832
+ let candidateTexts;
1833
+ if (input.candidateTexts && input.candidateTexts.length > 0) {
1834
+ candidateTexts = input.candidateTexts;
1835
+ } else {
1836
+ candidateTexts = await loadCandidateTexts(deps, input, limit, logger);
1837
+ if (candidateTexts.length === 0) {
1838
+ warnings.push({
1839
+ code: "derive_no_candidates",
1840
+ message: "No candidate fact texts available for derivation"
1841
+ });
1842
+ return { facts: [], warnings };
1843
+ }
1844
+ }
1845
+ let facts;
1846
+ try {
1847
+ facts = await deps.deriver({
1848
+ userId: input.userId,
1849
+ userScope: {
1850
+ userId: input.userId,
1851
+ botIds: input.botIds,
1852
+ dateFrom: input.dateFrom,
1853
+ dateTo: input.dateTo
1854
+ },
1855
+ recentFactTexts: candidateTexts,
1856
+ ...input.window ? { window: input.window } : {}
1857
+ });
1858
+ } catch (error) {
1859
+ logger.warn?.("[memory-store/derive] deriver threw:", error);
1860
+ warnings.push({
1861
+ code: "derive_deriver_failed",
1862
+ message: error.message ?? "deriver failed"
1863
+ });
1864
+ return { facts: [], warnings };
1865
+ }
1866
+ if (!Array.isArray(facts)) {
1867
+ warnings.push({
1868
+ code: "derive_deriver_returned_invalid_shape",
1869
+ message: `deriver returned ${typeof facts}; expected DerivedFact[]`
1870
+ });
1871
+ return { facts: [], warnings };
1872
+ }
1873
+ const normalized = [];
1874
+ const now = Date.now();
1875
+ for (const fact of facts) {
1876
+ if (!fact || typeof fact.text !== "string" || fact.text.length === 0 || typeof fact.kind !== "string" || !isDerivedKind(fact.kind) || !Array.isArray(fact.sources)) {
1877
+ continue;
1878
+ }
1879
+ normalized.push({
1880
+ ...fact,
1881
+ derivedAt: typeof fact.derivedAt === "number" ? fact.derivedAt : now,
1882
+ sources: fact.sources.filter((s) => typeof s === "string")
1883
+ });
1884
+ }
1885
+ if (input.persist && normalized.length > 0) {
1886
+ try {
1887
+ await input.persist(normalized);
1888
+ } catch (error) {
1889
+ logger.warn?.("[memory-store/derive] persist callback threw:", error);
1890
+ warnings.push({
1891
+ code: "derive_persist_failed",
1892
+ message: error.message ?? "derive persist failed"
1893
+ });
1894
+ }
1895
+ }
1896
+ return { facts: normalized, warnings };
1897
+ }
1898
+ async function loadCandidateTexts(deps, input, limit, logger) {
1899
+ if (typeof deps.searchRawMessagesLexical !== "function") {
1900
+ return [];
1901
+ }
1902
+ const queryText = (input.query ?? `${input.userId} ${input.botIds?.join(" ") ?? ""}`).trim();
1903
+ const keywords = deriveLexicalKeywords(queryText);
1904
+ if (keywords.length === 0) {
1905
+ return [];
1906
+ }
1907
+ try {
1908
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
1909
+ const factTypes = input.factTypes?.length ? input.factTypes : void 0;
1910
+ const hits = (await Promise.all(
1911
+ filters.map(
1912
+ (botId) => deps.searchRawMessagesLexical?.({
1913
+ userId: input.userId,
1914
+ keywords,
1915
+ limit: Math.ceil(limit / filters.length),
1916
+ botId,
1917
+ ...input.peers && input.peers.length > 0 ? { peers: input.peers } : {},
1918
+ ...factTypes ? { factTypes } : {}
1919
+ })
1920
+ )
1921
+ )).flat().filter(isRawMemorySemanticResult);
1922
+ return hits.map((h) => toMemoryResult(h).content);
1923
+ } catch (error) {
1924
+ logger.warn?.("[memory-store/derive] lexical candidate fetch failed:", error);
1925
+ return [];
1926
+ }
1927
+ }
1928
+ async function distillRawMessage(deps, input, logger = console) {
1929
+ const warnings = [];
1930
+ if (typeof deps.entityExtractor !== "function") {
1931
+ warnings.push({
1932
+ code: "distill_extractor_not_configured",
1933
+ message: "No `entityExtractor` is wired into the unified search deps; returning an empty entity list."
1934
+ });
1935
+ return { edges: [], warnings };
1936
+ }
1937
+ let edges;
1938
+ try {
1939
+ edges = await deps.entityExtractor({
1940
+ userId: input.userId,
1941
+ messageId: input.messageId,
1942
+ content: input.content
1943
+ });
1944
+ } catch (error) {
1945
+ logger.warn?.("[memory-store/distill] extractor threw:", error);
1946
+ warnings.push({
1947
+ code: "distill_extractor_failed",
1948
+ message: error.message ?? "entity extractor failed"
1949
+ });
1950
+ return { edges: [], warnings };
1951
+ }
1952
+ if (!Array.isArray(edges)) {
1953
+ warnings.push({
1954
+ code: "distill_extractor_returned_invalid_shape",
1955
+ message: `entityExtractor returned ${typeof edges}; expected EntityEdge[]`
1956
+ });
1957
+ return { edges: [], warnings };
1958
+ }
1959
+ const normalized = [];
1960
+ const now = Date.now();
1961
+ for (const edge of edges) {
1962
+ if (!edge || typeof edge.label !== "string" || edge.label.length === 0 || typeof edge.kind !== "string" || !isEntityKind(edge.kind) || typeof edge.relation !== "string" || typeof edge.sourceMessageId !== "string") {
1963
+ continue;
1964
+ }
1965
+ normalized.push({
1966
+ ...edge,
1967
+ label: edge.label.trim().toLowerCase(),
1968
+ extractedAt: typeof edge.extractedAt === "number" ? edge.extractedAt : now
1969
+ });
1970
+ }
1971
+ if (input.persist && normalized.length > 0) {
1972
+ try {
1973
+ await input.persist(normalized);
1974
+ } catch (error) {
1975
+ logger.warn?.("[memory-store/distill] persist callback threw:", error);
1976
+ warnings.push({
1977
+ code: "distill_persist_failed",
1978
+ message: error.message ?? "distill persist failed"
1979
+ });
1980
+ }
1981
+ }
1982
+ return { edges: normalized, warnings };
1983
+ }
1654
1984
  var DEFAULT_MAX_FACTS = 1e3;
1655
1985
  var LOW_CONFIDENCE_THRESHOLD = 0.1;
1656
1986
  function toHRR(data) {
@@ -2033,6 +2363,109 @@ async function startMcpServer(options = {}) {
2033
2363
  };
2034
2364
  }
2035
2365
  );
2366
+ const distillSchema = {
2367
+ userId: z.string(),
2368
+ messageId: z.string().min(1),
2369
+ content: z.string().min(1)
2370
+ };
2371
+ server.registerTool(
2372
+ "memory.distill",
2373
+ {
2374
+ title: "Distill Entities",
2375
+ description: "Extract `EntityEdge`s from a single raw message via the host's `unified.entityExtractor`. Returns `{ edges, warnings }`. Without the extractor wired in, returns an empty list with a `distill_extractor_not_configured` warning. Persistence is the host's responsibility \u2014 the MCP transport cannot carry host-side `persist` callbacks. Call `distillRawMessage` directly from host code if you need round-trip persistence.",
2376
+ // biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
2377
+ inputSchema: distillSchema
2378
+ },
2379
+ async (args) => {
2380
+ const a = args;
2381
+ const input = {
2382
+ userId: a.userId,
2383
+ messageId: a.messageId,
2384
+ content: a.content
2385
+ };
2386
+ try {
2387
+ const result = await distillRawMessage(options.unified ?? {}, input, console);
2388
+ return {
2389
+ content: [{ type: "text", text: JSON.stringify(result) }]
2390
+ };
2391
+ } catch (error) {
2392
+ console.error("[memory-store/mcp] distill failed:", error);
2393
+ return {
2394
+ content: [
2395
+ {
2396
+ type: "text",
2397
+ text: JSON.stringify({ error: error.message ?? "distill failed" })
2398
+ }
2399
+ ],
2400
+ isError: true
2401
+ };
2402
+ }
2403
+ }
2404
+ );
2405
+ const deriveSchema = {
2406
+ userId: z.string(),
2407
+ query: z.string().optional().describe(
2408
+ "Optional topical query used to derive lexical keywords when `candidateTexts` is omitted. Loop-engine schedulers should pass the topic they want synthesized facts about (e.g. 'cat preferences'). Without it the lexical fallback uses `userId + botIds` which is rarely useful."
2409
+ ),
2410
+ botIds: z.array(z.string()).optional(),
2411
+ peers: z.array(
2412
+ z.object({
2413
+ kind: z.enum(["user", "agent"]),
2414
+ id: z.string().min(1)
2415
+ })
2416
+ ).optional().describe("Optional peer scope forwarded to the lexical candidate-fetch fallback."),
2417
+ factTypes: z.array(z.string()).optional().describe("Optional FactType filter forwarded to the lexical candidate-fetch fallback."),
2418
+ dateFrom: z.string().optional(),
2419
+ dateTo: z.string().optional(),
2420
+ windowFrom: z.number().optional().describe("Optional time-window start (ms) forwarded to the deriver."),
2421
+ windowTo: z.number().optional().describe("Optional time-window end (ms) forwarded to the deriver."),
2422
+ candidateTexts: z.array(z.string()).optional().describe(
2423
+ "Pre-computed candidate fact texts. When omitted, the SDK pulls up to `candidateLimit` (default 50) texts via the lexical sub-query."
2424
+ ),
2425
+ candidateLimit: z.number().int().min(1).max(500).optional()
2426
+ };
2427
+ server.registerTool(
2428
+ "memory.derive",
2429
+ {
2430
+ title: "Derive Facts",
2431
+ description: "Synthesize `DerivedFact`s over a window of candidate fact texts via the host's `unified.deriver`. Returns `{ facts, warnings }`. Without the deriver wired in, returns an empty list with a `derive_deriver_not_configured` warning. Persistence is the host's responsibility \u2014 the MCP transport cannot carry host-side `persist` callbacks. Call `deriveFacts` directly from host code if you need round-trip persistence.",
2432
+ // biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
2433
+ inputSchema: deriveSchema
2434
+ },
2435
+ async (args) => {
2436
+ const a = args;
2437
+ const window = a.windowFrom !== void 0 && a.windowTo !== void 0 ? { from: a.windowFrom, to: a.windowTo } : void 0;
2438
+ const input = {
2439
+ userId: a.userId,
2440
+ ...a.query ? { query: a.query } : {},
2441
+ botIds: a.botIds,
2442
+ ...a.peers && a.peers.length > 0 ? { peers: a.peers } : {},
2443
+ ...a.factTypes && a.factTypes.length > 0 ? { factTypes: a.factTypes.filter(isFactType) } : {},
2444
+ dateFrom: a.dateFrom,
2445
+ dateTo: a.dateTo,
2446
+ ...window ? { window } : {},
2447
+ ...a.candidateTexts ? { candidateTexts: a.candidateTexts } : {},
2448
+ ...a.candidateLimit !== void 0 ? { candidateLimit: a.candidateLimit } : {}
2449
+ };
2450
+ try {
2451
+ const result = await deriveFacts(options.unified ?? {}, input, console);
2452
+ return {
2453
+ content: [{ type: "text", text: JSON.stringify(result) }]
2454
+ };
2455
+ } catch (error) {
2456
+ console.error("[memory-store/mcp] derive failed:", error);
2457
+ return {
2458
+ content: [
2459
+ {
2460
+ type: "text",
2461
+ text: JSON.stringify({ error: error.message ?? "derive failed" })
2462
+ }
2463
+ ],
2464
+ isError: true
2465
+ };
2466
+ }
2467
+ }
2468
+ );
2036
2469
  const vsaVocabularyEntry = {
2037
2470
  label: z.string().min(1),
2038
2471
  vector: z.array(z.number()).describe("Float32 vector for the vocabulary entry.")
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { U as UnifiedSearchDeps } from '../config-CnbqSek5.js';
2
+ import { U as UnifiedSearchDeps } from '../config-DfvVZ--0.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
6
+ import '@melandlabs/contracts/derived-fact';
7
+ import '@melandlabs/contracts/entity-edge';
6
8
 
7
9
  /**
8
10
  * Shared argv parsing + `unified.*` wiring for the memory-store CLI bins.
@@ -1,7 +1,9 @@
1
- import { a as MemoryStoreEnv } from '../config-CnbqSek5.js';
1
+ import { a as MemoryStoreEnv } from '../config-DfvVZ--0.js';
2
2
  import '@melandlabs/memory-consolidation';
3
3
  import '@melandlabs/contracts/peer';
4
4
  import '@melandlabs/contracts';
5
+ import '@melandlabs/contracts/derived-fact';
6
+ import '@melandlabs/contracts/entity-edge';
5
7
 
6
8
  /**
7
9
  * Lazy registration slot for the host's Postgres-backed raw message
@@ -1,8 +1,10 @@
1
1
  import { RawMessageStorageManager } from '@melandlabs/indexeddb';
2
- import { a as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-CnbqSek5.js';
2
+ import { a as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-DfvVZ--0.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
6
+ import '@melandlabs/contracts/derived-fact';
7
+ import '@melandlabs/contracts/entity-edge';
6
8
 
7
9
  /**
8
10
  * Top-level raw-message store facade.
@@ -1,8 +1,10 @@
1
1
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
2
- import { a as MemoryStoreEnv } from '../config-CnbqSek5.js';
2
+ import { a as MemoryStoreEnv } from '../config-DfvVZ--0.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
6
+ import '@melandlabs/contracts/derived-fact';
7
+ import '@melandlabs/contracts/entity-edge';
6
8
 
7
9
  /**
8
10
  * SQLite-backed raw message store.
@@ -1,8 +1,10 @@
1
1
  import { VectorSearchResult } from '@melandlabs/rag/vector-service';
2
- import { a as MemoryStoreEnv } from '../config-CnbqSek5.js';
2
+ import { a as MemoryStoreEnv } from '../config-DfvVZ--0.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
6
+ import '@melandlabs/contracts/derived-fact';
7
+ import '@melandlabs/contracts/entity-edge';
6
8
 
7
9
  /**
8
10
  * SQLite-vec-backed raw-message + insight vector index.
@@ -1,4 +1,4 @@
1
- import { S as SearchTier, b as UnifiedMemorySearchWarning, c as SearchEvidence, U as UnifiedSearchDeps, d as SearchInput, e as SearchOutput, f as UnifiedMemorySearchInput, g as UnifiedMemorySearchOutput, h as UnifiedMemorySearchResult } from './config-CnbqSek5.js';
1
+ import { S as SearchTier, b as UnifiedMemorySearchWarning, c as SearchEvidence, U as UnifiedSearchDeps, d as SearchInput, e as SearchOutput, f as UnifiedMemorySearchInput, g as UnifiedMemorySearchOutput, h as UnifiedMemorySearchResult } from './config-DfvVZ--0.js';
2
2
  import { MemoryStorageAdapter } from '@melandlabs/ai/memory';
3
3
  import { Peer } from '@melandlabs/contracts/peer';
4
4
  import { OwnerScope, MemoryConsolidationPlan, ReflectApplyClusterKeyFn, MemoryStorageAdapterLike, MemoryGraphStoreWithOperationHistory, MemoryGraphUpdateResult } from '@melandlabs/memory-consolidation';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melandlabs/memory-store",
3
- "version": "1.1.8",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -60,10 +60,10 @@
60
60
  "@modelcontextprotocol/sdk": "^1.25.3",
61
61
  "hono": "^4.6.14",
62
62
  "zod": "^4.3.6",
63
- "@melandlabs/contracts": "0.6.0",
64
63
  "@melandlabs/ai": "0.9.1",
65
- "@melandlabs/memory-consolidation": "0.5.2",
66
64
  "@melandlabs/env-config": "0.4.0",
65
+ "@melandlabs/contracts": "0.7.0",
66
+ "@melandlabs/memory-consolidation": "0.5.2",
67
67
  "@melandlabs/okf": "0.2.1",
68
68
  "@melandlabs/rag": "0.3.0",
69
69
  "@melandlabs/shared": "0.4.0",
@@ -71,8 +71,8 @@
71
71
  "@melandlabs/vsa": "0.3.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "@melandlabs/ai-rag": "0.2.9",
75
- "@melandlabs/indexeddb": "0.5.8"
74
+ "@melandlabs/indexeddb": "0.5.8",
75
+ "@melandlabs/ai-rag": "0.2.9"
76
76
  },
77
77
  "peerDependenciesMeta": {
78
78
  "@melandlabs/ai-rag": {