mem-os-python 1.6.27__py3-none-any.whl

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.
Files changed (253) hide show
  1. mem_os_python-1.6.27.dist-info/METADATA +1032 -0
  2. mem_os_python-1.6.27.dist-info/RECORD +253 -0
  3. mem_os_python-1.6.27.dist-info/WHEEL +4 -0
  4. mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
  5. mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
  6. memos/__init__.py +16 -0
  7. memos/_js/adapters/anthropic.d.ts +279 -0
  8. memos/_js/adapters/anthropic.d.ts.map +1 -0
  9. memos/_js/adapters/anthropic.js +348 -0
  10. memos/_js/adapters/anthropic.js.map +1 -0
  11. memos/_js/adapters/base.d.ts +10 -0
  12. memos/_js/adapters/base.d.ts.map +1 -0
  13. memos/_js/adapters/base.js +2 -0
  14. memos/_js/adapters/base.js.map +1 -0
  15. memos/_js/adapters/openai.d.ts +264 -0
  16. memos/_js/adapters/openai.d.ts.map +1 -0
  17. memos/_js/adapters/openai.js +342 -0
  18. memos/_js/adapters/openai.js.map +1 -0
  19. memos/_js/apps/explorer.d.ts +244 -0
  20. memos/_js/apps/explorer.d.ts.map +1 -0
  21. memos/_js/apps/explorer.js +738 -0
  22. memos/_js/apps/explorer.js.map +1 -0
  23. memos/_js/citations.d.ts +65 -0
  24. memos/_js/citations.d.ts.map +1 -0
  25. memos/_js/citations.js +143 -0
  26. memos/_js/citations.js.map +1 -0
  27. memos/_js/cli-browse.d.ts +3 -0
  28. memos/_js/cli-browse.d.ts.map +1 -0
  29. memos/_js/cli-browse.js +129 -0
  30. memos/_js/cli-browse.js.map +1 -0
  31. memos/_js/cli-completion.d.ts +31 -0
  32. memos/_js/cli-completion.d.ts.map +1 -0
  33. memos/_js/cli-completion.js +340 -0
  34. memos/_js/cli-completion.js.map +1 -0
  35. memos/_js/cli-help.d.ts +18 -0
  36. memos/_js/cli-help.d.ts.map +1 -0
  37. memos/_js/cli-help.js +357 -0
  38. memos/_js/cli-help.js.map +1 -0
  39. memos/_js/cli.d.ts +28 -0
  40. memos/_js/cli.d.ts.map +1 -0
  41. memos/_js/cli.js +2880 -0
  42. memos/_js/cli.js.map +1 -0
  43. memos/_js/compression.d.ts +25 -0
  44. memos/_js/compression.d.ts.map +1 -0
  45. memos/_js/compression.js +319 -0
  46. memos/_js/compression.js.map +1 -0
  47. memos/_js/confidence-machine.d.ts +99 -0
  48. memos/_js/confidence-machine.d.ts.map +1 -0
  49. memos/_js/confidence-machine.js +222 -0
  50. memos/_js/confidence-machine.js.map +1 -0
  51. memos/_js/context-pack.d.ts +506 -0
  52. memos/_js/context-pack.d.ts.map +1 -0
  53. memos/_js/context-pack.js +1435 -0
  54. memos/_js/context-pack.js.map +1 -0
  55. memos/_js/contradictions.d.ts +160 -0
  56. memos/_js/contradictions.d.ts.map +1 -0
  57. memos/_js/contradictions.js +255 -0
  58. memos/_js/contradictions.js.map +1 -0
  59. memos/_js/dashboard.d.ts +30 -0
  60. memos/_js/dashboard.d.ts.map +1 -0
  61. memos/_js/dashboard.js +302 -0
  62. memos/_js/dashboard.js.map +1 -0
  63. memos/_js/ebbinghaus.d.ts +29 -0
  64. memos/_js/ebbinghaus.d.ts.map +1 -0
  65. memos/_js/ebbinghaus.js +32 -0
  66. memos/_js/ebbinghaus.js.map +1 -0
  67. memos/_js/embedding-queue.d.ts +190 -0
  68. memos/_js/embedding-queue.d.ts.map +1 -0
  69. memos/_js/embedding-queue.js +316 -0
  70. memos/_js/embedding-queue.js.map +1 -0
  71. memos/_js/embeddings.d.ts +187 -0
  72. memos/_js/embeddings.d.ts.map +1 -0
  73. memos/_js/embeddings.js +633 -0
  74. memos/_js/embeddings.js.map +1 -0
  75. memos/_js/entity-extraction.d.ts +54 -0
  76. memos/_js/entity-extraction.d.ts.map +1 -0
  77. memos/_js/entity-extraction.js +325 -0
  78. memos/_js/entity-extraction.js.map +1 -0
  79. memos/_js/entity-index.d.ts +93 -0
  80. memos/_js/entity-index.d.ts.map +1 -0
  81. memos/_js/entity-index.js +165 -0
  82. memos/_js/entity-index.js.map +1 -0
  83. memos/_js/event-memory.d.ts +147 -0
  84. memos/_js/event-memory.d.ts.map +1 -0
  85. memos/_js/event-memory.js +290 -0
  86. memos/_js/event-memory.js.map +1 -0
  87. memos/_js/external-import.d.ts +98 -0
  88. memos/_js/external-import.d.ts.map +1 -0
  89. memos/_js/external-import.js +669 -0
  90. memos/_js/external-import.js.map +1 -0
  91. memos/_js/fidelity.d.ts +367 -0
  92. memos/_js/fidelity.d.ts.map +1 -0
  93. memos/_js/fidelity.js +911 -0
  94. memos/_js/fidelity.js.map +1 -0
  95. memos/_js/graph-expansion.d.ts +119 -0
  96. memos/_js/graph-expansion.d.ts.map +1 -0
  97. memos/_js/graph-expansion.js +213 -0
  98. memos/_js/graph-expansion.js.map +1 -0
  99. memos/_js/graph-mermaid.d.ts +13 -0
  100. memos/_js/graph-mermaid.d.ts.map +1 -0
  101. memos/_js/graph-mermaid.js +32 -0
  102. memos/_js/graph-mermaid.js.map +1 -0
  103. memos/_js/graph.d.ts +170 -0
  104. memos/_js/graph.d.ts.map +1 -0
  105. memos/_js/graph.js +482 -0
  106. memos/_js/graph.js.map +1 -0
  107. memos/_js/harness.d.ts +63 -0
  108. memos/_js/harness.d.ts.map +1 -0
  109. memos/_js/harness.js +93 -0
  110. memos/_js/harness.js.map +1 -0
  111. memos/_js/importance.d.ts +118 -0
  112. memos/_js/importance.d.ts.map +1 -0
  113. memos/_js/importance.js +144 -0
  114. memos/_js/importance.js.map +1 -0
  115. memos/_js/index.d.ts +51 -0
  116. memos/_js/index.d.ts.map +1 -0
  117. memos/_js/index.js +53 -0
  118. memos/_js/index.js.map +1 -0
  119. memos/_js/mcp.d.ts +58 -0
  120. memos/_js/mcp.d.ts.map +1 -0
  121. memos/_js/mcp.js +1853 -0
  122. memos/_js/mcp.js.map +1 -0
  123. memos/_js/memory.d.ts +1762 -0
  124. memos/_js/memory.d.ts.map +1 -0
  125. memos/_js/memory.js +6283 -0
  126. memos/_js/memory.js.map +1 -0
  127. memos/_js/procedural.d.ts +74 -0
  128. memos/_js/procedural.d.ts.map +1 -0
  129. memos/_js/procedural.js +125 -0
  130. memos/_js/procedural.js.map +1 -0
  131. memos/_js/project-config.d.ts +68 -0
  132. memos/_js/project-config.d.ts.map +1 -0
  133. memos/_js/project-config.js +59 -0
  134. memos/_js/project-config.js.map +1 -0
  135. memos/_js/proof-of-forgetting.d.ts +57 -0
  136. memos/_js/proof-of-forgetting.d.ts.map +1 -0
  137. memos/_js/proof-of-forgetting.js +80 -0
  138. memos/_js/proof-of-forgetting.js.map +1 -0
  139. memos/_js/provenance.d.ts +122 -0
  140. memos/_js/provenance.d.ts.map +1 -0
  141. memos/_js/provenance.js +182 -0
  142. memos/_js/provenance.js.map +1 -0
  143. memos/_js/quarantine-commands.d.ts +23 -0
  144. memos/_js/quarantine-commands.d.ts.map +1 -0
  145. memos/_js/quarantine-commands.js +42 -0
  146. memos/_js/quarantine-commands.js.map +1 -0
  147. memos/_js/quarantine.d.ts +120 -0
  148. memos/_js/quarantine.d.ts.map +1 -0
  149. memos/_js/quarantine.js +873 -0
  150. memos/_js/quarantine.js.map +1 -0
  151. memos/_js/redact.d.ts +12 -0
  152. memos/_js/redact.d.ts.map +1 -0
  153. memos/_js/redact.js +26 -0
  154. memos/_js/redact.js.map +1 -0
  155. memos/_js/repl.d.ts +57 -0
  156. memos/_js/repl.d.ts.map +1 -0
  157. memos/_js/repl.js +295 -0
  158. memos/_js/repl.js.map +1 -0
  159. memos/_js/rerank-local.d.ts +61 -0
  160. memos/_js/rerank-local.d.ts.map +1 -0
  161. memos/_js/rerank-local.js +210 -0
  162. memos/_js/rerank-local.js.map +1 -0
  163. memos/_js/retain-filter.d.ts +98 -0
  164. memos/_js/retain-filter.d.ts.map +1 -0
  165. memos/_js/retain-filter.js +232 -0
  166. memos/_js/retain-filter.js.map +1 -0
  167. memos/_js/retrieval.d.ts +265 -0
  168. memos/_js/retrieval.d.ts.map +1 -0
  169. memos/_js/retrieval.js +1018 -0
  170. memos/_js/retrieval.js.map +1 -0
  171. memos/_js/revert.d.ts +77 -0
  172. memos/_js/revert.d.ts.map +1 -0
  173. memos/_js/revert.js +252 -0
  174. memos/_js/revert.js.map +1 -0
  175. memos/_js/scope.d.ts +30 -0
  176. memos/_js/scope.d.ts.map +1 -0
  177. memos/_js/scope.js +71 -0
  178. memos/_js/scope.js.map +1 -0
  179. memos/_js/semantic-screen.d.ts +152 -0
  180. memos/_js/semantic-screen.d.ts.map +1 -0
  181. memos/_js/semantic-screen.js +290 -0
  182. memos/_js/semantic-screen.js.map +1 -0
  183. memos/_js/session-hooks.d.ts +127 -0
  184. memos/_js/session-hooks.d.ts.map +1 -0
  185. memos/_js/session-hooks.js +482 -0
  186. memos/_js/session-hooks.js.map +1 -0
  187. memos/_js/storage/sqlite.d.ts +427 -0
  188. memos/_js/storage/sqlite.d.ts.map +1 -0
  189. memos/_js/storage/sqlite.js +2428 -0
  190. memos/_js/storage/sqlite.js.map +1 -0
  191. memos/_js/sync.d.ts +213 -0
  192. memos/_js/sync.d.ts.map +1 -0
  193. memos/_js/sync.js +389 -0
  194. memos/_js/sync.js.map +1 -0
  195. memos/_js/tamper-log.d.ts +98 -0
  196. memos/_js/tamper-log.d.ts.map +1 -0
  197. memos/_js/tamper-log.js +327 -0
  198. memos/_js/tamper-log.js.map +1 -0
  199. memos/_js/temporal.d.ts +92 -0
  200. memos/_js/temporal.d.ts.map +1 -0
  201. memos/_js/temporal.js +536 -0
  202. memos/_js/temporal.js.map +1 -0
  203. memos/_js/time-travel.d.ts +38 -0
  204. memos/_js/time-travel.d.ts.map +1 -0
  205. memos/_js/time-travel.js +46 -0
  206. memos/_js/time-travel.js.map +1 -0
  207. memos/_js/timestamp-format.d.ts +18 -0
  208. memos/_js/timestamp-format.d.ts.map +1 -0
  209. memos/_js/timestamp-format.js +52 -0
  210. memos/_js/timestamp-format.js.map +1 -0
  211. memos/_js/triple-extraction.d.ts +31 -0
  212. memos/_js/triple-extraction.d.ts.map +1 -0
  213. memos/_js/triple-extraction.js +116 -0
  214. memos/_js/triple-extraction.js.map +1 -0
  215. memos/_js/types.d.ts +2474 -0
  216. memos/_js/types.d.ts.map +1 -0
  217. memos/_js/types.js +20 -0
  218. memos/_js/types.js.map +1 -0
  219. memos/_js/updater.d.ts +95 -0
  220. memos/_js/updater.d.ts.map +1 -0
  221. memos/_js/updater.js +268 -0
  222. memos/_js/updater.js.map +1 -0
  223. memos/_js/version.d.ts +17 -0
  224. memos/_js/version.d.ts.map +1 -0
  225. memos/_js/version.js +34 -0
  226. memos/_js/version.js.map +1 -0
  227. memos/_js/vision/clip-query-cache.d.ts +58 -0
  228. memos/_js/vision/clip-query-cache.d.ts.map +1 -0
  229. memos/_js/vision/clip-query-cache.js +79 -0
  230. memos/_js/vision/clip-query-cache.js.map +1 -0
  231. memos/_js/vision/clip.d.ts +113 -0
  232. memos/_js/vision/clip.d.ts.map +1 -0
  233. memos/_js/vision/clip.js +224 -0
  234. memos/_js/vision/clip.js.map +1 -0
  235. memos/_js/vision/fake-clip.d.ts +55 -0
  236. memos/_js/vision/fake-clip.d.ts.map +1 -0
  237. memos/_js/vision/fake-clip.js +122 -0
  238. memos/_js/vision/fake-clip.js.map +1 -0
  239. memos/_js/vision/index.d.ts +15 -0
  240. memos/_js/vision/index.d.ts.map +1 -0
  241. memos/_js/vision/index.js +13 -0
  242. memos/_js/vision/index.js.map +1 -0
  243. memos/_js/vision/store.d.ts +137 -0
  244. memos/_js/vision/store.d.ts.map +1 -0
  245. memos/_js/vision/store.js +383 -0
  246. memos/_js/vision/store.js.map +1 -0
  247. memos/adapters/__init__.py +0 -0
  248. memos/adapters/crewai.py +385 -0
  249. memos/adapters/langchain.py +301 -0
  250. memos/adapters/ollama.py +396 -0
  251. memos/server/__init__.py +0 -0
  252. memos/server/main.py +547 -0
  253. memos/server/routes.py +360 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Entity extraction for fused retrieval scoring.
3
+ *
4
+ * The third retrieval signal alongside keyword (FTS5) and semantic
5
+ * (embedding) legs: memories whose stored entities overlap the query's
6
+ * entities get a post-fusion boost. This mirrors the 2026 fused
7
+ * multi-signal retrieval consensus (semantic + BM25 + entity match).
8
+ *
9
+ * The extractor is deliberately lightweight and dependency-free —
10
+ * capitalized multi-word sequences, acronyms, quoted strings, and
11
+ * code-identifier shapes. It runs on every hybrid search, so it must
12
+ * stay in the sub-millisecond class. Entities for a memory are captured
13
+ * at write time into `metadata.entities` (see `MemOS.store`), so the
14
+ * per-candidate overlap check is a set intersection, not a rescan.
15
+ *
16
+ * @module @mem-os/entity-extraction
17
+ */
18
+ /**
19
+ * Extract candidate entities from free text.
20
+ *
21
+ * Returns lowercase-normalized entities so overlap checks are
22
+ * case-insensitive. Order is stable (first appearance), duplicates are
23
+ * removed. Sentence-initial capitalization is ignored (it's grammatical,
24
+ * not a name), which keeps "Fix the parser" from yielding "fix".
25
+ */
26
+ export declare function extractQueryEntities(text: string): string[];
27
+ /**
28
+ * Overlap between query entities and a memory's stored entities
29
+ * (`metadata.entities`) and tags, in [0, 1]. Jaccard-style: shared
30
+ * count over the smaller set, so a single match on a 1-entity query
31
+ * counts fully.
32
+ */
33
+ export declare function entityOverlap(queryEntities: readonly string[], node: {
34
+ tags?: readonly string[];
35
+ metadata?: {
36
+ entities?: unknown;
37
+ };
38
+ }): number;
39
+ /**
40
+ * Built-in alias table mapping surface forms to a canonical entity.
41
+ * Lexical extraction alone yields "Postgres", "PostgreSQL", and "pg" as
42
+ * three different entities; canonicalizing through this map (plus any
43
+ * deployment-specific `MemOSConfig.entityAliases`) collapses them so the
44
+ * overlap signal fires across phrasings.
45
+ */
46
+ export declare const BUILTIN_ENTITY_ALIASES: Readonly<Record<string, string>>;
47
+ /**
48
+ * Canonicalize extracted entities through the built-in alias table plus
49
+ * any deployment-specific aliases (from `MemOSConfig.entityAliases`,
50
+ * which wins over built-ins). Deduplicates after mapping, preserving
51
+ * first-appearance order.
52
+ */
53
+ export declare function canonicalizeEntities(entities: readonly string[], extraAliases?: Readonly<Record<string, string>>): string[];
54
+ //# sourceMappingURL=entity-extraction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-extraction.d.ts","sourceRoot":"","sources":["../src/entity-extraction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAwHH;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0E3D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACpE,MAAM,CA+BR;AAMD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAuCnE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,MAAM,EAAE,CAeV"}
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Entity extraction for fused retrieval scoring.
3
+ *
4
+ * The third retrieval signal alongside keyword (FTS5) and semantic
5
+ * (embedding) legs: memories whose stored entities overlap the query's
6
+ * entities get a post-fusion boost. This mirrors the 2026 fused
7
+ * multi-signal retrieval consensus (semantic + BM25 + entity match).
8
+ *
9
+ * The extractor is deliberately lightweight and dependency-free —
10
+ * capitalized multi-word sequences, acronyms, quoted strings, and
11
+ * code-identifier shapes. It runs on every hybrid search, so it must
12
+ * stay in the sub-millisecond class. Entities for a memory are captured
13
+ * at write time into `metadata.entities` (see `MemOS.store`), so the
14
+ * per-candidate overlap check is a set intersection, not a rescan.
15
+ *
16
+ * @module @mem-os/entity-extraction
17
+ */
18
+ /** Minimum length for a single-word entity (filters noise like "I", "A"). */
19
+ const MIN_WORD_LENGTH = 3;
20
+ /** Words that are capitalized mid-sentence but are not entities. */
21
+ const STOPWORDS = new Set([
22
+ "the",
23
+ "this",
24
+ "that",
25
+ "these",
26
+ "those",
27
+ "there",
28
+ "then",
29
+ "than",
30
+ "when",
31
+ "while",
32
+ "where",
33
+ "which",
34
+ "who",
35
+ "how",
36
+ "why",
37
+ "what",
38
+ "and",
39
+ "but",
40
+ "for",
41
+ "not",
42
+ "with",
43
+ "from",
44
+ "into",
45
+ "onto",
46
+ "over",
47
+ "after",
48
+ "before",
49
+ "because",
50
+ "if",
51
+ "it",
52
+ "its",
53
+ "we",
54
+ "our",
55
+ "you",
56
+ "your",
57
+ "they",
58
+ "their",
59
+ "she",
60
+ "his",
61
+ "her",
62
+ "him",
63
+ "us",
64
+ "them",
65
+ "please",
66
+ "also",
67
+ "just",
68
+ "very",
69
+ "can",
70
+ "could",
71
+ "should",
72
+ "would",
73
+ "will",
74
+ "shall",
75
+ "may",
76
+ "might",
77
+ "must",
78
+ "do",
79
+ "does",
80
+ "did",
81
+ "done",
82
+ "have",
83
+ "has",
84
+ "had",
85
+ "was",
86
+ "were",
87
+ "are",
88
+ "is",
89
+ "am",
90
+ "be",
91
+ "been",
92
+ "being",
93
+ "add",
94
+ "use",
95
+ "using",
96
+ "used",
97
+ "make",
98
+ "made",
99
+ "run",
100
+ "runs",
101
+ "new",
102
+ "old",
103
+ "get",
104
+ "got",
105
+ "set",
106
+ "let",
107
+ "put",
108
+ "try",
109
+ "tried",
110
+ "i'd",
111
+ "i'm",
112
+ "i've",
113
+ "don't",
114
+ "doesn't",
115
+ "didn't",
116
+ "won't",
117
+ "can't",
118
+ ]);
119
+ /** Terms that never count as entities regardless of casing. */
120
+ const NON_ENTITY_TOKEN = /^[0-9_]+$/;
121
+ function isCodeIdentifier(token) {
122
+ // snake_case, kebab-case, dotted paths, or CamelCase with an internal
123
+ // uppercase — the shapes that show up when agents store technical memory.
124
+ return /[_\-.]/.test(token) || /^[a-z]+[A-Z]/.test(token);
125
+ }
126
+ function isAcronym(token) {
127
+ return (token.length >= 2 && token === token.toUpperCase() && /[A-Z]/.test(token));
128
+ }
129
+ /**
130
+ * Extract candidate entities from free text.
131
+ *
132
+ * Returns lowercase-normalized entities so overlap checks are
133
+ * case-insensitive. Order is stable (first appearance), duplicates are
134
+ * removed. Sentence-initial capitalization is ignored (it's grammatical,
135
+ * not a name), which keeps "Fix the parser" from yielding "fix".
136
+ */
137
+ export function extractQueryEntities(text) {
138
+ if (!text)
139
+ return [];
140
+ const entities = [];
141
+ const seen = new Set();
142
+ const push = (raw, minLen = MIN_WORD_LENGTH) => {
143
+ const normalized = raw.toLowerCase().trim();
144
+ if (normalized.length < minLen)
145
+ return;
146
+ if (NON_ENTITY_TOKEN.test(normalized))
147
+ return;
148
+ if (STOPWORDS.has(normalized))
149
+ return;
150
+ if (seen.has(normalized))
151
+ return;
152
+ seen.add(normalized);
153
+ entities.push(normalized);
154
+ };
155
+ // 1. Quoted strings and backticked code spans are always entities.
156
+ // Single-character delimiters with lazy content (no `+` quantifiers
157
+ // adjacent to the negated class): nothing can be matched two ways, so
158
+ // the scan stays linear on adversarial, quote-heavy input — a
159
+ // polynomial-backtracking hazard with the previous `["'`]+` form.
160
+ for (const match of text.matchAll(/["'`]([^"'`\n]{3,60}?)["'`]/g)) {
161
+ push(match[1]);
162
+ }
163
+ // 2. Code identifiers (snake_case / kebab-case / dotted / camelCase).
164
+ for (const token of text.split(/[^A-Za-z0-9_\-.]+/)) {
165
+ if (token.length >= MIN_WORD_LENGTH &&
166
+ isCodeIdentifier(token) &&
167
+ !STOPWORDS.has(token.toLowerCase())) {
168
+ push(token);
169
+ }
170
+ }
171
+ // 3. Capitalized sequences (proper nouns), skipping sentence starts.
172
+ // A token is "sentence-initial" when the previous non-space char is
173
+ // nothing, `.`, `!`, `?`, or a newline.
174
+ const tokens = [...text.matchAll(/[A-Za-z][A-Za-z0-9'_-]*/g)];
175
+ for (let i = 0; i < tokens.length; i += 1) {
176
+ const match = tokens[i];
177
+ const token = match[0];
178
+ const prevChar = match.index && match.index > 0 ? text[match.index - 1] : "";
179
+ const sentenceInitial = match.index === 0 || /[.!?\n]/.test(prevChar) || prevChar === "\n";
180
+ if (isAcronym(token)) {
181
+ // Acronyms are high-signal at any length (CI, KV, DB) — the
182
+ // general minimum length only guards prose words.
183
+ push(token, 2);
184
+ continue;
185
+ }
186
+ if (sentenceInitial)
187
+ continue;
188
+ if (!/^[A-Z]/.test(token))
189
+ continue;
190
+ if (STOPWORDS.has(token.toLowerCase()))
191
+ continue;
192
+ // Extend across adjacent capitalized words ("Postgres Connection
193
+ // Pool") into one entity.
194
+ let phrase = token;
195
+ let lastIndex = match.index + token.length;
196
+ for (let j = i + 1; j < tokens.length; j += 1) {
197
+ const next = tokens[j];
198
+ const between = text.slice(lastIndex, next.index);
199
+ if (between !== " ")
200
+ break;
201
+ if (!/^[A-Z]/.test(next[0]) || STOPWORDS.has(next[0].toLowerCase()))
202
+ break;
203
+ phrase += ` ${next[0]}`;
204
+ lastIndex = next.index + next[0].length;
205
+ }
206
+ push(phrase);
207
+ }
208
+ return entities;
209
+ }
210
+ /**
211
+ * Overlap between query entities and a memory's stored entities
212
+ * (`metadata.entities`) and tags, in [0, 1]. Jaccard-style: shared
213
+ * count over the smaller set, so a single match on a 1-entity query
214
+ * counts fully.
215
+ */
216
+ export function entityOverlap(queryEntities, node) {
217
+ if (queryEntities.length === 0)
218
+ return 0;
219
+ const stored = new Set();
220
+ for (const tag of node.tags ?? []) {
221
+ stored.add(String(tag).toLowerCase());
222
+ }
223
+ const metaEntities = node.metadata?.entities;
224
+ if (Array.isArray(metaEntities)) {
225
+ for (const entity of metaEntities) {
226
+ stored.add(String(entity).toLowerCase());
227
+ }
228
+ }
229
+ if (stored.size === 0)
230
+ return 0;
231
+ let shared = 0;
232
+ for (const entity of queryEntities) {
233
+ // Substring match both ways so "postgres connection pool" matches a
234
+ // memory tagged "postgres" and vice versa.
235
+ for (const candidate of stored) {
236
+ if (candidate === entity ||
237
+ candidate.includes(entity) ||
238
+ entity.includes(candidate)) {
239
+ shared += 1;
240
+ break;
241
+ }
242
+ }
243
+ }
244
+ return shared / Math.min(queryEntities.length, stored.size);
245
+ }
246
+ // ---------------------------------------------------------------------------
247
+ // Entity resolution (aliasing)
248
+ // ---------------------------------------------------------------------------
249
+ /**
250
+ * Built-in alias table mapping surface forms to a canonical entity.
251
+ * Lexical extraction alone yields "Postgres", "PostgreSQL", and "pg" as
252
+ * three different entities; canonicalizing through this map (plus any
253
+ * deployment-specific `MemOSConfig.entityAliases`) collapses them so the
254
+ * overlap signal fires across phrasings.
255
+ */
256
+ export const BUILTIN_ENTITY_ALIASES = {
257
+ pg: "postgres",
258
+ postgres: "postgres",
259
+ postgresql: "postgres",
260
+ pgsql: "postgres",
261
+ js: "javascript",
262
+ nodejs: "node",
263
+ "node.js": "node",
264
+ ts: "typescript",
265
+ k8s: "kubernetes",
266
+ ks: "kubernetes",
267
+ py: "python",
268
+ pip: "python",
269
+ yarn: "npm",
270
+ npm: "npm",
271
+ docker: "docker",
272
+ containerd: "docker",
273
+ rust: "rust",
274
+ go: "golang",
275
+ golang: "golang",
276
+ rails: "rails",
277
+ react: "react",
278
+ nextjs: "next",
279
+ "next.js": "next",
280
+ vue: "vue",
281
+ s3: "aws",
282
+ aws: "aws",
283
+ gcp: "gcp",
284
+ gh: "github",
285
+ github: "github",
286
+ gha: "github-actions",
287
+ llm: "llm",
288
+ agent: "agent",
289
+ agents: "agent",
290
+ db: "database",
291
+ database: "database",
292
+ redis: "redis",
293
+ sqlite: "sqlite",
294
+ graphql: "graphql",
295
+ };
296
+ /**
297
+ * Canonicalize extracted entities through the built-in alias table plus
298
+ * any deployment-specific aliases (from `MemOSConfig.entityAliases`,
299
+ * which wins over built-ins). Deduplicates after mapping, preserving
300
+ * first-appearance order.
301
+ */
302
+ export function canonicalizeEntities(entities, extraAliases) {
303
+ const aliases = extraAliases
304
+ ? { ...BUILTIN_ENTITY_ALIASES, ...lowerKeys(extraAliases) }
305
+ : BUILTIN_ENTITY_ALIASES;
306
+ const out = [];
307
+ const seen = new Set();
308
+ for (const entity of entities) {
309
+ const key = entity.toLowerCase();
310
+ const canonical = (aliases[key] ?? key).toLowerCase();
311
+ if (seen.has(canonical))
312
+ continue;
313
+ seen.add(canonical);
314
+ out.push(canonical);
315
+ }
316
+ return out;
317
+ }
318
+ function lowerKeys(record) {
319
+ const out = {};
320
+ for (const [key, value] of Object.entries(record)) {
321
+ out[key.toLowerCase()] = value.toLowerCase();
322
+ }
323
+ return out;
324
+ }
325
+ //# sourceMappingURL=entity-extraction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-extraction.js","sourceRoot":"","sources":["../src/entity-extraction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * First-class entity index: an entity → memories inverted table in SQLite.
3
+ *
4
+ * MemOS already had an entity *signal* — the post-fusion entity-overlap
5
+ * boost in `fuseResults` (`src/retrieval.ts`) — but it could only re-score
6
+ * candidates the keyword (FTS5) and semantic legs had already retrieved.
7
+ * Memories that share entities with the query yet were missed by both
8
+ * legs never entered the candidate pool at all. This module closes that
9
+ * recall gap: every node's canonical entities are mirrored into the
10
+ * `entity_index` table at write time (following the `node_tags` join-table
11
+ * pattern), and `searchByEntities` turns query entities into a ranked
12
+ * candidate list that `MemOS.hybridSearch` fuses as a third RRF leg.
13
+ *
14
+ * This is the local, no-LLM analogue of the entity channel behind mem0
15
+ * v3's reported +20/+26 point jump and HippoRAG's entity-node gains: the
16
+ * extractor is the existing dependency-free lexical one
17
+ * (`src/entity-extraction.ts`), and the index is a plain B-tree — no
18
+ * embedding or graph traversal on the hot path.
19
+ *
20
+ * @module @mem-os/entity-index
21
+ */
22
+ import type Database from "better-sqlite3";
23
+ /** One ranked hit from {@link searchByEntities}. */
24
+ export interface EntityIndexMatch {
25
+ /** The matched node's id. */
26
+ nodeId: string;
27
+ /** How many of the query entities this node matched. */
28
+ matches: number;
29
+ }
30
+ /**
31
+ * Create the `entity_index` inverted table and its lookup index when
32
+ * missing. Migration-safe (`IF NOT EXISTS`): pre-existing databases gain
33
+ * the table on the next `init()` with no schema-version bump, and
34
+ * {@link backfillEntityIndex} populates it from existing rows.
35
+ *
36
+ * Node deletions cascade (`ON DELETE CASCADE` + `PRAGMA foreign_keys =
37
+ * ON`), so no explicit cleanup is needed on the delete path.
38
+ */
39
+ export declare function ensureEntityIndexTable(db: Database.Database): void;
40
+ /**
41
+ * Extract + canonicalize entities from free text (lowercase-normalized,
42
+ * deduped, first-appearance order). Used when no pre-canonicalized entity
43
+ * list is on hand — legacy rows in the backfill, or rows written straight
44
+ * through storage bypassing `MemOS.store`. `extraAliases` mirrors
45
+ * `MemOSConfig.entityAliases` (storage itself has no config, so the
46
+ * write/backfill paths pass none and get the built-in table).
47
+ */
48
+ export declare function extractContentEntities(content: string, extraAliases?: Readonly<Record<string, string>>): string[];
49
+ /**
50
+ * Normalize raw entity strings for index storage: lowercase + trim +
51
+ * dedupe, preserving first-appearance order. The index is always
52
+ * lowercase, so lookups are case-insensitive by construction.
53
+ */
54
+ export declare function normalizeEntities(entities: readonly unknown[]): string[];
55
+ /**
56
+ * Full re-sync of one node's entity rows: deletes stale rows, then
57
+ * inserts the normalized set (`INSERT OR IGNORE` keeps it idempotent).
58
+ * Runs inside the caller's transaction — call from `saveNode` /
59
+ * `updateNode` next to their `node_tags` mirrors.
60
+ */
61
+ export declare function writeEntityIndexRows(db: Database.Database, nodeId: string, entities: readonly unknown[]): void;
62
+ /**
63
+ * Index one node's entities from its content: extracts with
64
+ * `extractQueryEntities` + `canonicalizeEntities` and stores
65
+ * lowercase-normalized rows. Prefer passing the write-time canonicalized
66
+ * `metadata.entities` via {@link writeEntityIndexRows} when available
67
+ * (it was canonicalized with the deployment's `entityAliases`); this
68
+ * content-based variant is the fallback for rows that lack it.
69
+ */
70
+ export declare function indexNodeEntities(db: Database.Database, nodeId: string, content: string, extraAliases?: Readonly<Record<string, string>>): void;
71
+ /**
72
+ * Remove every index row for a node. Node deletes are normally handled by
73
+ * the `ON DELETE CASCADE` foreign key — this is for explicit re-syncs.
74
+ */
75
+ export declare function removeNodeEntities(db: Database.Database, nodeId: string): void;
76
+ /**
77
+ * Ranked entity-leg lookup: node ids whose indexed entities intersect
78
+ * `entities`, ordered by matched-entity count (desc) with node id as the
79
+ * deterministic tiebreak. Returns `{ nodeId, matches }` pairs, best
80
+ * first — the caller (hybrid search) resolves ids to nodes and fuses the
81
+ * list as the third RRF leg.
82
+ */
83
+ export declare function searchByEntities(db: Database.Database, entities: readonly string[], limit: number): EntityIndexMatch[];
84
+ /**
85
+ * One-time backfill for rows that predate the `entity_index` table (or
86
+ * lost their rows out-of-band). For each node missing from the index,
87
+ * prefers the stored `metadata.entities` — already canonicalized with the
88
+ * deployment's aliases at write time — and falls back to extracting from
89
+ * content. Idempotent: nodes already present in the index are skipped and
90
+ * inserts use `INSERT OR IGNORE`, so re-running is a no-op.
91
+ */
92
+ export declare function backfillEntityIndex(db: Database.Database): void;
93
+ //# sourceMappingURL=entity-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-index.d.ts","sourceRoot":"","sources":["../src/entity-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAM3C,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAUlE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,MAAM,EAAE,CAEV;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,EAAE,CAUxE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,SAAS,OAAO,EAAE,GAC3B,IAAI,CAUN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,IAAI,CAMN;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,MAAM,EAAE,MAAM,GACb,IAAI,CAEN;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,MAAM,GACZ,gBAAgB,EAAE,CAkBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAuC/D"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * First-class entity index: an entity → memories inverted table in SQLite.
3
+ *
4
+ * MemOS already had an entity *signal* — the post-fusion entity-overlap
5
+ * boost in `fuseResults` (`src/retrieval.ts`) — but it could only re-score
6
+ * candidates the keyword (FTS5) and semantic legs had already retrieved.
7
+ * Memories that share entities with the query yet were missed by both
8
+ * legs never entered the candidate pool at all. This module closes that
9
+ * recall gap: every node's canonical entities are mirrored into the
10
+ * `entity_index` table at write time (following the `node_tags` join-table
11
+ * pattern), and `searchByEntities` turns query entities into a ranked
12
+ * candidate list that `MemOS.hybridSearch` fuses as a third RRF leg.
13
+ *
14
+ * This is the local, no-LLM analogue of the entity channel behind mem0
15
+ * v3's reported +20/+26 point jump and HippoRAG's entity-node gains: the
16
+ * extractor is the existing dependency-free lexical one
17
+ * (`src/entity-extraction.ts`), and the index is a plain B-tree — no
18
+ * embedding or graph traversal on the hot path.
19
+ *
20
+ * @module @mem-os/entity-index
21
+ */
22
+ import { canonicalizeEntities, extractQueryEntities, } from "./entity-extraction.js";
23
+ /**
24
+ * Create the `entity_index` inverted table and its lookup index when
25
+ * missing. Migration-safe (`IF NOT EXISTS`): pre-existing databases gain
26
+ * the table on the next `init()` with no schema-version bump, and
27
+ * {@link backfillEntityIndex} populates it from existing rows.
28
+ *
29
+ * Node deletions cascade (`ON DELETE CASCADE` + `PRAGMA foreign_keys =
30
+ * ON`), so no explicit cleanup is needed on the delete path.
31
+ */
32
+ export function ensureEntityIndexTable(db) {
33
+ db.exec(`
34
+ CREATE TABLE IF NOT EXISTS entity_index (
35
+ entity TEXT NOT NULL,
36
+ node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
37
+ PRIMARY KEY (entity, node_id)
38
+ );
39
+ CREATE INDEX IF NOT EXISTS idx_entity_index_entity
40
+ ON entity_index(entity);
41
+ `);
42
+ }
43
+ /**
44
+ * Extract + canonicalize entities from free text (lowercase-normalized,
45
+ * deduped, first-appearance order). Used when no pre-canonicalized entity
46
+ * list is on hand — legacy rows in the backfill, or rows written straight
47
+ * through storage bypassing `MemOS.store`. `extraAliases` mirrors
48
+ * `MemOSConfig.entityAliases` (storage itself has no config, so the
49
+ * write/backfill paths pass none and get the built-in table).
50
+ */
51
+ export function extractContentEntities(content, extraAliases) {
52
+ return canonicalizeEntities(extractQueryEntities(content), extraAliases);
53
+ }
54
+ /**
55
+ * Normalize raw entity strings for index storage: lowercase + trim +
56
+ * dedupe, preserving first-appearance order. The index is always
57
+ * lowercase, so lookups are case-insensitive by construction.
58
+ */
59
+ export function normalizeEntities(entities) {
60
+ const out = [];
61
+ const seen = new Set();
62
+ for (const entity of entities) {
63
+ const normalized = String(entity).toLowerCase().trim();
64
+ if (normalized.length === 0 || seen.has(normalized))
65
+ continue;
66
+ seen.add(normalized);
67
+ out.push(normalized);
68
+ }
69
+ return out;
70
+ }
71
+ /**
72
+ * Full re-sync of one node's entity rows: deletes stale rows, then
73
+ * inserts the normalized set (`INSERT OR IGNORE` keeps it idempotent).
74
+ * Runs inside the caller's transaction — call from `saveNode` /
75
+ * `updateNode` next to their `node_tags` mirrors.
76
+ */
77
+ export function writeEntityIndexRows(db, nodeId, entities) {
78
+ const normalized = normalizeEntities(entities);
79
+ db.prepare("DELETE FROM entity_index WHERE node_id = ?").run(nodeId);
80
+ if (normalized.length === 0)
81
+ return;
82
+ const insert = db.prepare("INSERT OR IGNORE INTO entity_index (entity, node_id) VALUES (?, ?)");
83
+ for (const entity of normalized) {
84
+ insert.run(entity, nodeId);
85
+ }
86
+ }
87
+ /**
88
+ * Index one node's entities from its content: extracts with
89
+ * `extractQueryEntities` + `canonicalizeEntities` and stores
90
+ * lowercase-normalized rows. Prefer passing the write-time canonicalized
91
+ * `metadata.entities` via {@link writeEntityIndexRows} when available
92
+ * (it was canonicalized with the deployment's `entityAliases`); this
93
+ * content-based variant is the fallback for rows that lack it.
94
+ */
95
+ export function indexNodeEntities(db, nodeId, content, extraAliases) {
96
+ writeEntityIndexRows(db, nodeId, extractContentEntities(content, extraAliases));
97
+ }
98
+ /**
99
+ * Remove every index row for a node. Node deletes are normally handled by
100
+ * the `ON DELETE CASCADE` foreign key — this is for explicit re-syncs.
101
+ */
102
+ export function removeNodeEntities(db, nodeId) {
103
+ db.prepare("DELETE FROM entity_index WHERE node_id = ?").run(nodeId);
104
+ }
105
+ /**
106
+ * Ranked entity-leg lookup: node ids whose indexed entities intersect
107
+ * `entities`, ordered by matched-entity count (desc) with node id as the
108
+ * deterministic tiebreak. Returns `{ nodeId, matches }` pairs, best
109
+ * first — the caller (hybrid search) resolves ids to nodes and fuses the
110
+ * list as the third RRF leg.
111
+ */
112
+ export function searchByEntities(db, entities, limit) {
113
+ const normalized = normalizeEntities(entities);
114
+ if (normalized.length === 0 || limit <= 0)
115
+ return [];
116
+ const placeholders = normalized.map(() => "?").join(", ");
117
+ const rows = db
118
+ .prepare(`SELECT node_id AS nodeId, COUNT(*) AS matches
119
+ FROM entity_index
120
+ WHERE entity IN (${placeholders})
121
+ GROUP BY node_id
122
+ ORDER BY matches DESC, node_id ASC
123
+ LIMIT ?`)
124
+ .all(...normalized, limit);
125
+ return rows.map((row) => ({
126
+ nodeId: String(row.nodeId),
127
+ matches: Number(row.matches),
128
+ }));
129
+ }
130
+ /**
131
+ * One-time backfill for rows that predate the `entity_index` table (or
132
+ * lost their rows out-of-band). For each node missing from the index,
133
+ * prefers the stored `metadata.entities` — already canonicalized with the
134
+ * deployment's aliases at write time — and falls back to extracting from
135
+ * content. Idempotent: nodes already present in the index are skipped and
136
+ * inserts use `INSERT OR IGNORE`, so re-running is a no-op.
137
+ */
138
+ export function backfillEntityIndex(db) {
139
+ const indexed = new Set(db.prepare("SELECT node_id FROM entity_index").all().map((row) => row.node_id));
140
+ const rows = db
141
+ .prepare("SELECT id, content, metadata FROM nodes")
142
+ .all();
143
+ if (rows.length === 0)
144
+ return;
145
+ const tx = db.transaction(() => {
146
+ for (const row of rows) {
147
+ if (indexed.has(row.id))
148
+ continue;
149
+ let entities = null;
150
+ try {
151
+ const metadata = JSON.parse(row.metadata);
152
+ if (Array.isArray(metadata?.entities)) {
153
+ entities = metadata.entities;
154
+ }
155
+ }
156
+ catch {
157
+ // Corrupt metadata JSON: fall through to content extraction.
158
+ }
159
+ writeEntityIndexRows(db, row.id, entities ?? extractContentEntities(row.content));
160
+ indexed.add(row.id);
161
+ }
162
+ });
163
+ tx();
164
+ }
165
+ //# sourceMappingURL=entity-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-index.js","sourceRoot":"","sources":["../src/entity-index.ts"],"names":[],"mappings":"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"}