cogxai 2.8.0

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 (308) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +691 -0
  3. package/dist/agents/executor.d.ts +17 -0
  4. package/dist/agents/executor.d.ts.map +1 -0
  5. package/dist/agents/executor.js +336 -0
  6. package/dist/agents/executor.js.map +1 -0
  7. package/dist/agents/index.d.ts +9 -0
  8. package/dist/agents/index.d.ts.map +1 -0
  9. package/dist/agents/index.js +18 -0
  10. package/dist/agents/index.js.map +1 -0
  11. package/dist/agents/tools.d.ts +31 -0
  12. package/dist/agents/tools.d.ts.map +1 -0
  13. package/dist/agents/tools.js +262 -0
  14. package/dist/agents/tools.js.map +1 -0
  15. package/dist/agents/types.d.ts +25 -0
  16. package/dist/agents/types.d.ts.map +1 -0
  17. package/dist/agents/types.js +170 -0
  18. package/dist/agents/types.js.map +1 -0
  19. package/dist/character/agent-tier-modifiers.d.ts +3 -0
  20. package/dist/character/agent-tier-modifiers.d.ts.map +1 -0
  21. package/dist/character/agent-tier-modifiers.js +24 -0
  22. package/dist/character/agent-tier-modifiers.js.map +1 -0
  23. package/dist/character/base-prompt.d.ts +3 -0
  24. package/dist/character/base-prompt.d.ts.map +1 -0
  25. package/dist/character/base-prompt.js +45 -0
  26. package/dist/character/base-prompt.js.map +1 -0
  27. package/dist/character/mood-modifiers.d.ts +3 -0
  28. package/dist/character/mood-modifiers.d.ts.map +1 -0
  29. package/dist/character/mood-modifiers.js +27 -0
  30. package/dist/character/mood-modifiers.js.map +1 -0
  31. package/dist/character/tier-modifiers.d.ts +3 -0
  32. package/dist/character/tier-modifiers.d.ts.map +1 -0
  33. package/dist/character/tier-modifiers.js +25 -0
  34. package/dist/character/tier-modifiers.js.map +1 -0
  35. package/dist/cli/banner.d.ts +20 -0
  36. package/dist/cli/banner.d.ts.map +1 -0
  37. package/dist/cli/banner.js +73 -0
  38. package/dist/cli/banner.js.map +1 -0
  39. package/dist/cli/export.d.ts +2 -0
  40. package/dist/cli/export.d.ts.map +1 -0
  41. package/dist/cli/export.js +176 -0
  42. package/dist/cli/export.js.map +1 -0
  43. package/dist/cli/index.d.ts +3 -0
  44. package/dist/cli/index.d.ts.map +1 -0
  45. package/dist/cli/index.js +80 -0
  46. package/dist/cli/index.js.map +1 -0
  47. package/dist/cli/init.d.ts +2 -0
  48. package/dist/cli/init.d.ts.map +1 -0
  49. package/dist/cli/init.js +541 -0
  50. package/dist/cli/init.js.map +1 -0
  51. package/dist/cli/register.d.ts +2 -0
  52. package/dist/cli/register.d.ts.map +1 -0
  53. package/dist/cli/register.js +121 -0
  54. package/dist/cli/register.js.map +1 -0
  55. package/dist/cli/setup.d.ts +3 -0
  56. package/dist/cli/setup.d.ts.map +1 -0
  57. package/dist/cli/setup.js +403 -0
  58. package/dist/cli/setup.js.map +1 -0
  59. package/dist/cli/ship.d.ts +2 -0
  60. package/dist/cli/ship.d.ts.map +1 -0
  61. package/dist/cli/ship.js +32 -0
  62. package/dist/cli/ship.js.map +1 -0
  63. package/dist/config.d.ts +90 -0
  64. package/dist/config.d.ts.map +1 -0
  65. package/dist/config.js +124 -0
  66. package/dist/config.js.map +1 -0
  67. package/dist/core/allium-client.d.ts +32 -0
  68. package/dist/core/allium-client.d.ts.map +1 -0
  69. package/dist/core/allium-client.js +162 -0
  70. package/dist/core/allium-client.js.map +1 -0
  71. package/dist/core/claude-client.d.ts +31 -0
  72. package/dist/core/claude-client.d.ts.map +1 -0
  73. package/dist/core/claude-client.js +172 -0
  74. package/dist/core/claude-client.js.map +1 -0
  75. package/dist/core/database.d.ts +17 -0
  76. package/dist/core/database.d.ts.map +1 -0
  77. package/dist/core/database.js +499 -0
  78. package/dist/core/database.js.map +1 -0
  79. package/dist/core/embeddings.d.ts +28 -0
  80. package/dist/core/embeddings.d.ts.map +1 -0
  81. package/dist/core/embeddings.js +212 -0
  82. package/dist/core/embeddings.js.map +1 -0
  83. package/dist/core/encryption.d.ts +49 -0
  84. package/dist/core/encryption.d.ts.map +1 -0
  85. package/dist/core/encryption.js +136 -0
  86. package/dist/core/encryption.js.map +1 -0
  87. package/dist/core/guardrails.d.ts +30 -0
  88. package/dist/core/guardrails.d.ts.map +1 -0
  89. package/dist/core/guardrails.js +191 -0
  90. package/dist/core/guardrails.js.map +1 -0
  91. package/dist/core/inference.d.ts +37 -0
  92. package/dist/core/inference.d.ts.map +1 -0
  93. package/dist/core/inference.js +110 -0
  94. package/dist/core/inference.js.map +1 -0
  95. package/dist/core/input-guardrails.d.ts +21 -0
  96. package/dist/core/input-guardrails.d.ts.map +1 -0
  97. package/dist/core/input-guardrails.js +59 -0
  98. package/dist/core/input-guardrails.js.map +1 -0
  99. package/dist/core/logger.d.ts +3 -0
  100. package/dist/core/logger.d.ts.map +1 -0
  101. package/dist/core/logger.js +41 -0
  102. package/dist/core/logger.js.map +1 -0
  103. package/dist/core/memory-graph.d.ts +126 -0
  104. package/dist/core/memory-graph.d.ts.map +1 -0
  105. package/dist/core/memory-graph.js +451 -0
  106. package/dist/core/memory-graph.js.map +1 -0
  107. package/dist/core/memory.d.ts +172 -0
  108. package/dist/core/memory.d.ts.map +1 -0
  109. package/dist/core/memory.js +1432 -0
  110. package/dist/core/memory.js.map +1 -0
  111. package/dist/core/owner-context.d.ts +8 -0
  112. package/dist/core/owner-context.d.ts.map +1 -0
  113. package/dist/core/owner-context.js +31 -0
  114. package/dist/core/owner-context.js.map +1 -0
  115. package/dist/core/price-oracle.d.ts +16 -0
  116. package/dist/core/price-oracle.d.ts.map +1 -0
  117. package/dist/core/price-oracle.js +162 -0
  118. package/dist/core/price-oracle.js.map +1 -0
  119. package/dist/core/solana-client.d.ts +39 -0
  120. package/dist/core/solana-client.d.ts.map +1 -0
  121. package/dist/core/solana-client.js +366 -0
  122. package/dist/core/solana-client.js.map +1 -0
  123. package/dist/core/telegram-client.d.ts +22 -0
  124. package/dist/core/telegram-client.d.ts.map +1 -0
  125. package/dist/core/telegram-client.js +93 -0
  126. package/dist/core/telegram-client.js.map +1 -0
  127. package/dist/core/venice-client.d.ts +94 -0
  128. package/dist/core/venice-client.d.ts.map +1 -0
  129. package/dist/core/venice-client.js +282 -0
  130. package/dist/core/venice-client.js.map +1 -0
  131. package/dist/core/x-client.d.ts +68 -0
  132. package/dist/core/x-client.d.ts.map +1 -0
  133. package/dist/core/x-client.js +383 -0
  134. package/dist/core/x-client.js.map +1 -0
  135. package/dist/events/event-bus.d.ts +37 -0
  136. package/dist/events/event-bus.d.ts.map +1 -0
  137. package/dist/events/event-bus.js +21 -0
  138. package/dist/events/event-bus.js.map +1 -0
  139. package/dist/events/handlers.d.ts +6 -0
  140. package/dist/events/handlers.d.ts.map +1 -0
  141. package/dist/events/handlers.js +21 -0
  142. package/dist/events/handlers.js.map +1 -0
  143. package/dist/features/action-learning.d.ts +61 -0
  144. package/dist/features/action-learning.d.ts.map +1 -0
  145. package/dist/features/action-learning.js +424 -0
  146. package/dist/features/action-learning.js.map +1 -0
  147. package/dist/features/active-reflection.d.ts +37 -0
  148. package/dist/features/active-reflection.d.ts.map +1 -0
  149. package/dist/features/active-reflection.js +376 -0
  150. package/dist/features/active-reflection.js.map +1 -0
  151. package/dist/features/agent-tier.d.ts +21 -0
  152. package/dist/features/agent-tier.d.ts.map +1 -0
  153. package/dist/features/agent-tier.js +56 -0
  154. package/dist/features/agent-tier.js.map +1 -0
  155. package/dist/features/campaign-tracker.d.ts +8 -0
  156. package/dist/features/campaign-tracker.d.ts.map +1 -0
  157. package/dist/features/campaign-tracker.js +206 -0
  158. package/dist/features/campaign-tracker.js.map +1 -0
  159. package/dist/features/clinamen.d.ts +48 -0
  160. package/dist/features/clinamen.d.ts.map +1 -0
  161. package/dist/features/clinamen.js +164 -0
  162. package/dist/features/clinamen.js.map +1 -0
  163. package/dist/features/dream-cycle.d.ts +12 -0
  164. package/dist/features/dream-cycle.d.ts.map +1 -0
  165. package/dist/features/dream-cycle.js +889 -0
  166. package/dist/features/dream-cycle.js.map +1 -0
  167. package/dist/features/market-monitor.d.ts +4 -0
  168. package/dist/features/market-monitor.d.ts.map +1 -0
  169. package/dist/features/market-monitor.js +135 -0
  170. package/dist/features/market-monitor.js.map +1 -0
  171. package/dist/features/memory-trace.d.ts +75 -0
  172. package/dist/features/memory-trace.d.ts.map +1 -0
  173. package/dist/features/memory-trace.js +325 -0
  174. package/dist/features/memory-trace.js.map +1 -0
  175. package/dist/features/onchain-opinion.d.ts +5 -0
  176. package/dist/features/onchain-opinion.d.ts.map +1 -0
  177. package/dist/features/onchain-opinion.js +70 -0
  178. package/dist/features/onchain-opinion.js.map +1 -0
  179. package/dist/features/price-personality.d.ts +4 -0
  180. package/dist/features/price-personality.d.ts.map +1 -0
  181. package/dist/features/price-personality.js +156 -0
  182. package/dist/features/price-personality.js.map +1 -0
  183. package/dist/features/x-sentiment-monitor.d.ts +8 -0
  184. package/dist/features/x-sentiment-monitor.d.ts.map +1 -0
  185. package/dist/features/x-sentiment-monitor.js +289 -0
  186. package/dist/features/x-sentiment-monitor.js.map +1 -0
  187. package/dist/index.d.ts +3 -0
  188. package/dist/index.d.ts.map +1 -0
  189. package/dist/index.js +133 -0
  190. package/dist/index.js.map +1 -0
  191. package/dist/knowledge/tokenomics.d.ts +72 -0
  192. package/dist/knowledge/tokenomics.d.ts.map +1 -0
  193. package/dist/knowledge/tokenomics.js +140 -0
  194. package/dist/knowledge/tokenomics.js.map +1 -0
  195. package/dist/mcp/local-store.d.ts +53 -0
  196. package/dist/mcp/local-store.d.ts.map +1 -0
  197. package/dist/mcp/local-store.js +175 -0
  198. package/dist/mcp/local-store.js.map +1 -0
  199. package/dist/mcp/server.d.ts +2 -0
  200. package/dist/mcp/server.d.ts.map +1 -0
  201. package/dist/mcp/server.js +373 -0
  202. package/dist/mcp/server.js.map +1 -0
  203. package/dist/mentions/classifier.d.ts +3 -0
  204. package/dist/mentions/classifier.d.ts.map +1 -0
  205. package/dist/mentions/classifier.js +51 -0
  206. package/dist/mentions/classifier.js.map +1 -0
  207. package/dist/mentions/dispatcher.d.ts +3 -0
  208. package/dist/mentions/dispatcher.d.ts.map +1 -0
  209. package/dist/mentions/dispatcher.js +404 -0
  210. package/dist/mentions/dispatcher.js.map +1 -0
  211. package/dist/mentions/poller.d.ts +3 -0
  212. package/dist/mentions/poller.d.ts.map +1 -0
  213. package/dist/mentions/poller.js +45 -0
  214. package/dist/mentions/poller.js.map +1 -0
  215. package/dist/sdk/cortex-v2.d.ts +121 -0
  216. package/dist/sdk/cortex-v2.d.ts.map +1 -0
  217. package/dist/sdk/cortex-v2.js +207 -0
  218. package/dist/sdk/cortex-v2.js.map +1 -0
  219. package/dist/sdk/cortex.d.ts +64 -0
  220. package/dist/sdk/cortex.d.ts.map +1 -0
  221. package/dist/sdk/cortex.js +362 -0
  222. package/dist/sdk/cortex.js.map +1 -0
  223. package/dist/sdk/http-transport.d.ts +15 -0
  224. package/dist/sdk/http-transport.d.ts.map +1 -0
  225. package/dist/sdk/http-transport.js +49 -0
  226. package/dist/sdk/http-transport.js.map +1 -0
  227. package/dist/sdk/index.d.ts +5 -0
  228. package/dist/sdk/index.d.ts.map +1 -0
  229. package/dist/sdk/index.js +8 -0
  230. package/dist/sdk/index.js.map +1 -0
  231. package/dist/sdk/sdk-mode.d.ts +1 -0
  232. package/dist/sdk/sdk-mode.d.ts.map +1 -0
  233. package/dist/sdk/sdk-mode.js +5 -0
  234. package/dist/sdk/sdk-mode.js.map +1 -0
  235. package/dist/sdk/types.d.ts +48 -0
  236. package/dist/sdk/types.d.ts.map +1 -0
  237. package/dist/sdk/types.js +3 -0
  238. package/dist/sdk/types.js.map +1 -0
  239. package/dist/services/response.service.d.ts +27 -0
  240. package/dist/services/response.service.d.ts.map +1 -0
  241. package/dist/services/response.service.js +62 -0
  242. package/dist/services/response.service.js.map +1 -0
  243. package/dist/services/social.service.d.ts +14 -0
  244. package/dist/services/social.service.d.ts.map +1 -0
  245. package/dist/services/social.service.js +44 -0
  246. package/dist/services/social.service.js.map +1 -0
  247. package/dist/services/telegram.service.d.ts +30 -0
  248. package/dist/services/telegram.service.d.ts.map +1 -0
  249. package/dist/services/telegram.service.js +71 -0
  250. package/dist/services/telegram.service.js.map +1 -0
  251. package/dist/types/api.d.ts +109 -0
  252. package/dist/types/api.d.ts.map +1 -0
  253. package/dist/types/api.js +6 -0
  254. package/dist/types/api.js.map +1 -0
  255. package/dist/utils/constants.d.ts +49 -0
  256. package/dist/utils/constants.d.ts.map +1 -0
  257. package/dist/utils/constants.js +110 -0
  258. package/dist/utils/constants.js.map +1 -0
  259. package/dist/utils/env-persona.d.ts +9 -0
  260. package/dist/utils/env-persona.d.ts.map +1 -0
  261. package/dist/utils/env-persona.js +53 -0
  262. package/dist/utils/env-persona.js.map +1 -0
  263. package/dist/utils/format.d.ts +12 -0
  264. package/dist/utils/format.d.ts.map +1 -0
  265. package/dist/utils/format.js +59 -0
  266. package/dist/utils/format.js.map +1 -0
  267. package/dist/utils/index.d.ts +4 -0
  268. package/dist/utils/index.d.ts.map +1 -0
  269. package/dist/utils/index.js +20 -0
  270. package/dist/utils/index.js.map +1 -0
  271. package/dist/utils/text.d.ts +10 -0
  272. package/dist/utils/text.d.ts.map +1 -0
  273. package/dist/utils/text.js +33 -0
  274. package/dist/utils/text.js.map +1 -0
  275. package/dist/verify-app/routes.d.ts +3 -0
  276. package/dist/verify-app/routes.d.ts.map +1 -0
  277. package/dist/verify-app/routes.js +101 -0
  278. package/dist/verify-app/routes.js.map +1 -0
  279. package/dist/webhook/agent-routes.d.ts +3 -0
  280. package/dist/webhook/agent-routes.d.ts.map +1 -0
  281. package/dist/webhook/agent-routes.js +314 -0
  282. package/dist/webhook/agent-routes.js.map +1 -0
  283. package/dist/webhook/campaign-routes.d.ts +3 -0
  284. package/dist/webhook/campaign-routes.d.ts.map +1 -0
  285. package/dist/webhook/campaign-routes.js +333 -0
  286. package/dist/webhook/campaign-routes.js.map +1 -0
  287. package/dist/webhook/cortex-routes.d.ts +13 -0
  288. package/dist/webhook/cortex-routes.d.ts.map +1 -0
  289. package/dist/webhook/cortex-routes.js +534 -0
  290. package/dist/webhook/cortex-routes.js.map +1 -0
  291. package/dist/webhook/dashboard-routes.d.ts +8 -0
  292. package/dist/webhook/dashboard-routes.d.ts.map +1 -0
  293. package/dist/webhook/dashboard-routes.js +588 -0
  294. package/dist/webhook/dashboard-routes.js.map +1 -0
  295. package/dist/webhook/graph-routes.d.ts +3 -0
  296. package/dist/webhook/graph-routes.d.ts.map +1 -0
  297. package/dist/webhook/graph-routes.js +238 -0
  298. package/dist/webhook/graph-routes.js.map +1 -0
  299. package/dist/webhook/privy-auth.d.ts +35 -0
  300. package/dist/webhook/privy-auth.d.ts.map +1 -0
  301. package/dist/webhook/privy-auth.js +99 -0
  302. package/dist/webhook/privy-auth.js.map +1 -0
  303. package/dist/webhook/server.d.ts +4 -0
  304. package/dist/webhook/server.d.ts.map +1 -0
  305. package/dist/webhook/server.js +912 -0
  306. package/dist/webhook/server.js.map +1 -0
  307. package/package.json +96 -0
  308. package/supabase-schema.sql +399 -0
@@ -0,0 +1,1432 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._setOwnerWallet = _setOwnerWallet;
4
+ exports.getOwnerWallet = getOwnerWallet;
5
+ exports.scopeToOwner = scopeToOwner;
6
+ exports.generateHashId = generateHashId;
7
+ exports.isValidHashId = isValidHashId;
8
+ exports.inferConcepts = inferConcepts;
9
+ exports.storeMemory = storeMemory;
10
+ exports.recallMemories = recallMemories;
11
+ exports.scoreMemory = scoreMemory;
12
+ exports.recallMemorySummaries = recallMemorySummaries;
13
+ exports.hydrateMemories = hydrateMemories;
14
+ exports.createMemoryLink = createMemoryLink;
15
+ exports.decayMemories = decayMemories;
16
+ exports.getMemoryStats = getMemoryStats;
17
+ exports.getRecentMemories = getRecentMemories;
18
+ exports.getSelfModel = getSelfModel;
19
+ exports.storeDreamLog = storeDreamLog;
20
+ exports.formatMemoryContext = formatMemoryContext;
21
+ exports.calculateImportance = calculateImportance;
22
+ exports.scoreImportanceWithLLM = scoreImportanceWithLLM;
23
+ exports.moodToValence = moodToValence;
24
+ const database_1 = require("./database");
25
+ const logger_1 = require("./logger");
26
+ const utils_1 = require("../utils");
27
+ const claude_client_1 = require("./claude-client");
28
+ const solana_client_1 = require("./solana-client");
29
+ const embeddings_1 = require("./embeddings");
30
+ const venice_client_1 = require("./venice-client");
31
+ const encryption_1 = require("./encryption");
32
+ const event_bus_1 = require("../events/event-bus");
33
+ // ---- EMBEDDING CACHE ---- //
34
+ const EMBED_CACHE_MAX = 200;
35
+ const embeddingCache = new Map();
36
+ function getCachedEmbedding(query) {
37
+ const entry = embeddingCache.get(query);
38
+ if (entry && Date.now() - entry.ts < 5 * 60 * 1000)
39
+ return entry.embedding; // 5 min TTL
40
+ return null;
41
+ }
42
+ function setCachedEmbedding(query, embedding) {
43
+ if (embeddingCache.size >= EMBED_CACHE_MAX) {
44
+ // Evict oldest
45
+ let oldest = Infinity, oldKey = '';
46
+ for (const [k, v] of embeddingCache) {
47
+ if (v.ts < oldest) {
48
+ oldest = v.ts;
49
+ oldKey = k;
50
+ }
51
+ }
52
+ if (oldKey)
53
+ embeddingCache.delete(oldKey);
54
+ }
55
+ embeddingCache.set(query, { embedding, ts: Date.now() });
56
+ }
57
+ const crypto_1 = require("crypto");
58
+ const memory_graph_1 = require("./memory-graph");
59
+ const owner_context_1 = require("./owner-context");
60
+ // ---- OWNER WALLET ---- //
61
+ let _ownerWallet = null;
62
+ /** @internal Set the owner wallet address for tagging memories. */
63
+ function _setOwnerWallet(wallet) {
64
+ _ownerWallet = wallet;
65
+ }
66
+ /** Get the configured owner wallet, if any. Checks async context first (hosted API), then module-level (SDK/bot). */
67
+ function getOwnerWallet() {
68
+ // AsyncLocalStorage takes priority (request-scoped for hosted API)
69
+ const contextWallet = (0, owner_context_1.getContextOwnerWallet)();
70
+ if (contextWallet !== undefined)
71
+ return contextWallet;
72
+ // Fallback to module-level (SDK / main bot)
73
+ return _ownerWallet;
74
+ }
75
+ /**
76
+ * Apply owner_wallet scoping to a Supabase query builder.
77
+ * When an owner wallet is set, filters to only that wallet's memories.
78
+ * When null, no filter is applied (backward-compatible).
79
+ */
80
+ function scopeToOwner(query) {
81
+ const wallet = getOwnerWallet();
82
+ if (wallet) {
83
+ return query.eq('owner_wallet', wallet);
84
+ }
85
+ return query;
86
+ }
87
+ // ============================================================
88
+ // HASH-BASED IDs (Beads-inspired)
89
+ //
90
+ // Generate short, collision-resistant IDs like "cogxai-a1b2c3d4"
91
+ // instead of sequential integers. Benefits:
92
+ // - No merge conflicts when multiple agents create memories
93
+ // - IDs remain stable across database migrations
94
+ // - Human-readable and URL-safe
95
+ // ============================================================
96
+ const HASH_ID_PREFIX = 'cogxai';
97
+ /**
98
+ * Generate a collision-resistant hash ID for a memory.
99
+ * Format: cogxai-xxxxxxxx (8 hex chars = 4 bytes = 4 billion possibilities)
100
+ */
101
+ function generateHashId() {
102
+ return `${HASH_ID_PREFIX}-${(0, crypto_1.randomBytes)(4).toString('hex')}`;
103
+ }
104
+ /**
105
+ * Validate a hash ID format.
106
+ */
107
+ function isValidHashId(id) {
108
+ return /^cogxai-[a-f0-9]{8}$/.test(id);
109
+ }
110
+ const log = (0, logger_1.createChildLogger)('memory');
111
+ // ---- CONCEPT ONTOLOGY ---- //
112
+ /**
113
+ * Auto-classify memories into structured concepts using keyword heuristics.
114
+ * Provides consistent cross-cutting knowledge labels without LLM cost.
115
+ * Concepts are additive to freeform tags, not a replacement.
116
+ */
117
+ function inferConcepts(summary, source, tags) {
118
+ const concepts = [];
119
+ const lower = summary.toLowerCase();
120
+ const tagSet = new Set(tags.map(t => t.toLowerCase()));
121
+ if (source === 'market' || tagSet.has('price') || /price|pump|dump|ath|market|volume/.test(lower))
122
+ concepts.push('market_event');
123
+ if (/whale|holder|seller|buyer|exit|accumula/.test(lower))
124
+ concepts.push('holder_behavior');
125
+ if (source === 'reflection' || source === 'emergence' || /myself|i am|i feel|identity|who i/.test(lower))
126
+ concepts.push('self_insight');
127
+ if (source === 'mention' || /tweet|reply|said|asked|mentioned|dm/.test(lower))
128
+ concepts.push('social_interaction');
129
+ if (/pattern|trend|recurring|always|usually|community/.test(lower))
130
+ concepts.push('community_pattern');
131
+ if (/token|sol|mint|swap|transfer|liquidity|staking/.test(lower))
132
+ concepts.push('token_economics');
133
+ if (/mood|sentiment|feel|vibe|energy|atmosphere/.test(lower))
134
+ concepts.push('sentiment_shift');
135
+ if (tagSet.has('first_interaction') || /returning|regular|again|came back/.test(lower))
136
+ concepts.push('recurring_user');
137
+ if (/whale|large|massive|huge|big (buy|sell)/.test(lower))
138
+ concepts.push('whale_activity');
139
+ if (/price|chart|candle|volume|mcap|cap/.test(lower))
140
+ concepts.push('price_action');
141
+ if (/engagement|likes|retweet|viral|reach|impressions/.test(lower))
142
+ concepts.push('engagement_pattern');
143
+ if (source === 'emergence' || /becoming|evolving|changed|grew|identity/.test(lower))
144
+ concepts.push('identity_evolution');
145
+ return [...new Set(concepts)];
146
+ }
147
+ // ---- STORE ---- //
148
+ async function storeMemory(opts) {
149
+ const db = (0, database_1.getDb)();
150
+ // Auto-classify concepts if not explicitly provided
151
+ const concepts = opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []);
152
+ // Generate collision-resistant hash ID (Beads-inspired)
153
+ const hashId = generateHashId();
154
+ try {
155
+ // Encrypt content if encryption is enabled (content only — summary/tags/metadata stay plaintext)
156
+ const plaintextContent = opts.content.slice(0, utils_1.MEMORY_MAX_CONTENT_LENGTH);
157
+ const shouldEncrypt = (0, encryption_1.isEncryptionEnabled)();
158
+ const storedContent = shouldEncrypt ? (0, encryption_1.encryptContent)(plaintextContent) : plaintextContent;
159
+ const { data, error } = await db
160
+ .from('memories')
161
+ .insert({
162
+ hash_id: hashId,
163
+ memory_type: opts.type,
164
+ content: storedContent,
165
+ summary: opts.summary.slice(0, utils_1.MEMORY_MAX_SUMMARY_LENGTH),
166
+ tags: opts.tags || [],
167
+ concepts,
168
+ emotional_valence: (0, utils_1.clamp)(opts.emotionalValence ?? 0, -1, 1),
169
+ importance: (0, utils_1.clamp)(opts.importance ?? 0.5, 0, 1),
170
+ source: opts.source,
171
+ source_id: opts.sourceId || null,
172
+ related_user: opts.relatedUser || null,
173
+ related_wallet: opts.relatedWallet || null,
174
+ metadata: opts.metadata || {},
175
+ evidence_ids: opts.evidenceIds || [],
176
+ compacted: false,
177
+ encrypted: shouldEncrypt,
178
+ encryption_pubkey: shouldEncrypt ? (0, encryption_1.getEncryptionPubkey)() : null,
179
+ owner_wallet: getOwnerWallet() || null,
180
+ })
181
+ .select('id, hash_id')
182
+ .single();
183
+ if (error) {
184
+ log.error({ error: error.message }, 'Failed to store memory');
185
+ return null;
186
+ }
187
+ log.debug({
188
+ id: data.id,
189
+ hashId: data.hash_id,
190
+ type: opts.type,
191
+ summary: opts.summary.slice(0, 60),
192
+ importance: opts.importance,
193
+ concepts,
194
+ }, 'Memory stored');
195
+ // Notify reflection trigger system (Park et al. 2023 — event-driven reflection)
196
+ event_bus_1.eventBus.emit('memory:stored', {
197
+ importance: (0, utils_1.clamp)(opts.importance ?? 0.5, 0, 1),
198
+ memoryType: opts.type,
199
+ });
200
+ // Commit memory to Solana (fire-and-forget)
201
+ commitMemoryToChain(data.id, opts).catch(err => log.warn({ err }, 'On-chain memory commit failed'));
202
+ // Generate embeddings and store fragments (fire-and-forget)
203
+ embedMemory(data.id, opts).catch(err => log.warn({ err }, 'Embedding generation failed'));
204
+ // Auto-link to related memories (fire-and-forget, runs after embedding is available)
205
+ autoLinkMemory(data.id, opts).catch(err => log.warn({ err }, 'Auto-linking failed'));
206
+ // Extract entities and build knowledge graph (fire-and-forget)
207
+ extractAndLinkEntitiesForMemory(data.id, opts).catch(err => log.debug({ err }, 'Entity extraction failed'));
208
+ return data.id;
209
+ }
210
+ catch (err) {
211
+ log.error({ err }, 'Memory store failed');
212
+ return null;
213
+ }
214
+ }
215
+ // ---- ON-CHAIN COMMIT ---- //
216
+ async function commitMemoryToChain(memoryId, opts) {
217
+ // Skip mainnet commits for demo memories (they use devnet instead)
218
+ if (opts.source === 'demo' || opts.source === 'demo-maas')
219
+ return;
220
+ const contentHashBuf = (0, crypto_1.createHash)('sha256').update(opts.content).digest();
221
+ let signature = null;
222
+ // Try on-chain registry first, fall back to memo
223
+ if ((0, solana_client_1.isRegistryEnabled)()) {
224
+ const encrypted = (0, encryption_1.isEncryptionEnabled)();
225
+ signature = await (0, solana_client_1.registerMemoryOnChain)(contentHashBuf, opts.type, opts.importance ?? 0.5, memoryId, encrypted);
226
+ }
227
+ // Fallback to memo if registry unavailable or failed
228
+ if (!signature) {
229
+ const contentHashHex = contentHashBuf.toString('hex');
230
+ const memo = `cogxai-memory | id: ${memoryId} | type: ${opts.type} | hash: ${contentHashHex.slice(0, 16)} | ${opts.summary.slice(0, 400)}`;
231
+ signature = await (0, solana_client_1.writeMemo)(memo);
232
+ }
233
+ if (!signature)
234
+ return;
235
+ const db = (0, database_1.getDb)();
236
+ await db
237
+ .from('memories')
238
+ .update({ solana_signature: signature })
239
+ .eq('id', memoryId);
240
+ log.debug({ memoryId, signature: signature.slice(0, 16) }, 'Memory committed on-chain');
241
+ }
242
+ // ---- EMBEDDING & GRANULAR DECOMPOSITION ---- //
243
+ /**
244
+ * Generate vector embedding for a memory and decompose into semantic fragments.
245
+ * Each fragment gets its own embedding for precise sub-memory retrieval.
246
+ *
247
+ * Fragment types:
248
+ * summary — the memory's summary (always stored)
249
+ * content_chunk — content split at natural boundaries
250
+ * tag_context — tags + concepts as a descriptive sentence
251
+ */
252
+ async function embedMemory(memoryId, opts) {
253
+ if (!(0, embeddings_1.isEmbeddingEnabled)())
254
+ return;
255
+ const db = (0, database_1.getDb)();
256
+ // Build fragment texts for granular decomposition
257
+ const fragments = [];
258
+ // Fragment 1: Summary (always)
259
+ fragments.push({ type: 'summary', text: opts.summary });
260
+ // Fragment 2+: Content chunks (split at sentence/paragraph boundaries)
261
+ const content = opts.content.slice(0, utils_1.MEMORY_MAX_CONTENT_LENGTH);
262
+ if (content.length > utils_1.EMBEDDING_FRAGMENT_MAX_LENGTH) {
263
+ const sentences = content.match(/[^.!?\n]+[.!?\n]+/g) || [content];
264
+ let chunk = '';
265
+ for (const sentence of sentences) {
266
+ if (chunk.length + sentence.length > utils_1.EMBEDDING_FRAGMENT_MAX_LENGTH && chunk.length > 0) {
267
+ fragments.push({ type: 'content_chunk', text: chunk.trim() });
268
+ chunk = '';
269
+ }
270
+ chunk += sentence;
271
+ }
272
+ if (chunk.trim())
273
+ fragments.push({ type: 'content_chunk', text: chunk.trim() });
274
+ }
275
+ else {
276
+ fragments.push({ type: 'content_chunk', text: content });
277
+ }
278
+ // Fragment 3: Tag/concept context as natural language
279
+ const allLabels = [...(opts.tags || []), ...(opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []))];
280
+ if (allLabels.length > 0) {
281
+ fragments.push({ type: 'tag_context', text: `Context: ${allLabels.join(', ')}. ${opts.summary}` });
282
+ }
283
+ // Batch-generate all embeddings
284
+ const embeddings = await (0, embeddings_1.generateEmbeddings)(fragments.map(f => f.text));
285
+ // Store primary embedding on the memory itself (summary embedding)
286
+ const summaryEmbedding = embeddings[0];
287
+ if (summaryEmbedding) {
288
+ await db
289
+ .from('memories')
290
+ .update({ embedding: JSON.stringify(summaryEmbedding) })
291
+ .eq('id', memoryId);
292
+ }
293
+ // Store all fragments with their embeddings
294
+ const fragmentRows = fragments.map((f, i) => ({
295
+ memory_id: memoryId,
296
+ fragment_type: f.type,
297
+ content: f.text.slice(0, utils_1.EMBEDDING_FRAGMENT_MAX_LENGTH),
298
+ embedding: embeddings[i] ? JSON.stringify(embeddings[i]) : null,
299
+ })).filter(r => r.embedding !== null);
300
+ if (fragmentRows.length > 0) {
301
+ const { error } = await db.from('memory_fragments').insert(fragmentRows);
302
+ if (error) {
303
+ log.warn({ error: error.message, memoryId }, 'Failed to store memory fragments');
304
+ }
305
+ }
306
+ log.debug({ memoryId, fragments: fragmentRows.length }, 'Memory embedded with fragments');
307
+ }
308
+ // ---- RECALL ---- //
309
+ /**
310
+ * Hybrid retrieval combining vector similarity, keyword matching, and structured scoring.
311
+ *
312
+ * When embeddings are available:
313
+ * 1. Generate query embedding
314
+ * 2. Run vector search (memory-level + fragment-level) for semantic candidates
315
+ * 3. Merge with metadata-filtered candidates from Supabase
316
+ * 4. Score all candidates with enhanced composite formula
317
+ *
318
+ * When embeddings are unavailable:
319
+ * Falls back to existing keyword + tag + importance scoring.
320
+ *
321
+ * score = (w_recency * recency + w_relevance * relevance + w_importance * importance
322
+ * + w_vector * vector_similarity) * decay_factor
323
+ */
324
+ // ---- QUERY EXPANSION ---- //
325
+ /**
326
+ * Expand a query into multiple search angles using a fast LLM.
327
+ * Returns the original query + 2-3 reformulations for broader vector coverage.
328
+ * Falls back to just the original query if LLM is unavailable or slow.
329
+ */
330
+ async function expandQuery(query) {
331
+ if (!(0, venice_client_1.isVeniceEnabled)())
332
+ return [query];
333
+ try {
334
+ const response = await Promise.race([
335
+ (0, venice_client_1.generateVeniceResponse)({
336
+ systemPrompt: 'You are a search query expander. Given a question, output 3 alternative phrasings that would help find relevant information in a memory database. Output ONLY the 3 alternatives, one per line. No numbering, no explanations.',
337
+ messages: [{ role: 'user', content: query }],
338
+ model: 'llama-3.2-3b',
339
+ maxTokens: 150,
340
+ temperature: 0.3,
341
+ cognitiveFunction: 'entity', // Use fast model slot
342
+ }),
343
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
344
+ ]);
345
+ const expansions = response
346
+ .split('\n')
347
+ .map(l => l.trim())
348
+ .filter(l => l.length > 5 && l.length < 200)
349
+ .slice(0, 3);
350
+ log.debug({ original: query, expansions: expansions.length }, 'Query expanded');
351
+ return [query, ...expansions];
352
+ }
353
+ catch (err) {
354
+ log.debug({ err }, 'Query expansion failed, using original');
355
+ return [query];
356
+ }
357
+ }
358
+ async function recallMemories(opts) {
359
+ const db = (0, database_1.getDb)();
360
+ const limit = opts.limit || 5;
361
+ const minDecay = opts.minDecay ?? 0.1;
362
+ try {
363
+ // Phase 0: Query expansion — generate alternative phrasings for broader recall
364
+ const queries = opts.query && !opts._vectorScores && !opts.skipExpansion
365
+ ? await expandQuery(opts.query)
366
+ : opts.query ? [opts.query] : [];
367
+ // Phase 1+2: Vector search + metadata query IN PARALLEL
368
+ let vectorScores = opts._vectorScores || new Map();
369
+ let primaryQueryEmbedding = null; // Shared for Phase 2b seed scoring
370
+ // Start embedding immediately (non-blocking)
371
+ const vectorSearchPromise = (queries.length > 0 && (0, embeddings_1.isEmbeddingEnabled)() && !opts._vectorScores)
372
+ ? (async () => {
373
+ // Embed all query variants (with cache)
374
+ const queryEmbeddings = await Promise.all(queries.map(async (q) => {
375
+ const cached = getCachedEmbedding(q);
376
+ if (cached)
377
+ return cached;
378
+ const emb = await (0, embeddings_1.generateQueryEmbedding)(q);
379
+ if (emb)
380
+ setCachedEmbedding(q, emb);
381
+ return emb;
382
+ }));
383
+ const validEmbeddings = queryEmbeddings.filter((e) => e !== null);
384
+ if (validEmbeddings.length > 0)
385
+ primaryQueryEmbedding = validEmbeddings[0];
386
+ if (validEmbeddings.length === 0) {
387
+ log.debug('All query embeddings returned null, using keyword-only retrieval');
388
+ return;
389
+ }
390
+ try {
391
+ // Memory-level search only (skip fragments for speed when skipExpansion is set)
392
+ const allSearches = validEmbeddings.flatMap(emb => {
393
+ const searches = [
394
+ Promise.resolve(db.rpc('match_memories', {
395
+ query_embedding: JSON.stringify(emb),
396
+ match_threshold: utils_1.VECTOR_MATCH_THRESHOLD,
397
+ match_count: limit * (opts.skipExpansion ? 12 : 4),
398
+ filter_types: opts.memoryTypes || null,
399
+ filter_user: opts.relatedUser || null,
400
+ min_decay: minDecay,
401
+ filter_owner: getOwnerWallet() || null,
402
+ })).then(r => r.data || []),
403
+ ];
404
+ // Only search fragments when not in fast mode
405
+ if (!opts.skipExpansion) {
406
+ searches.push(Promise.resolve(db.rpc('match_memory_fragments', {
407
+ query_embedding: JSON.stringify(emb),
408
+ match_threshold: utils_1.VECTOR_MATCH_THRESHOLD,
409
+ match_count: limit * 2,
410
+ filter_owner: getOwnerWallet() || null,
411
+ })).then(r => r.data || []));
412
+ }
413
+ return searches;
414
+ });
415
+ const results = await Promise.all(allSearches);
416
+ // Merge: take highest similarity per memory_id across ALL queries
417
+ if (opts.skipExpansion) {
418
+ // All results are memory-level matches (no fragments)
419
+ for (const batch of results) {
420
+ for (const m of batch) {
421
+ const current = vectorScores.get(m.id) || 0;
422
+ vectorScores.set(m.id, Math.max(current, m.similarity));
423
+ }
424
+ }
425
+ }
426
+ else {
427
+ for (let i = 0; i < results.length; i++) {
428
+ const hasFragments = validEmbeddings.length > 0;
429
+ const step = hasFragments ? 2 : 1;
430
+ if (i % step === 0) {
431
+ for (const m of results[i]) {
432
+ const current = vectorScores.get(m.id) || 0;
433
+ vectorScores.set(m.id, Math.max(current, m.similarity));
434
+ }
435
+ }
436
+ else {
437
+ for (const f of results[i]) {
438
+ const current = vectorScores.get(f.memory_id) || 0;
439
+ vectorScores.set(f.memory_id, Math.max(current, f.max_similarity));
440
+ }
441
+ }
442
+ }
443
+ }
444
+ log.debug({
445
+ queryVariants: validEmbeddings.length,
446
+ uniqueMemories: vectorScores.size,
447
+ fastMode: !!opts.skipExpansion,
448
+ }, 'Vector search completed');
449
+ }
450
+ catch (err) {
451
+ log.warn({ err }, 'Vector search RPC failed, falling back to keyword retrieval');
452
+ }
453
+ })()
454
+ : Promise.resolve();
455
+ // Phase 2: Metadata-filtered candidates from Supabase (runs IN PARALLEL with vector search)
456
+ // Run two queries in parallel: importance-ranked + text-search for diversity
457
+ let importanceQuery = db
458
+ .from('memories')
459
+ .select('*')
460
+ .gte('decay_factor', minDecay)
461
+ .not('source', 'in', '("demo","demo-maas")')
462
+ .order('importance', { ascending: false })
463
+ .order('created_at', { ascending: false })
464
+ .limit(limit * 3);
465
+ importanceQuery = scopeToOwner(importanceQuery);
466
+ if (opts.memoryTypes && opts.memoryTypes.length > 0) {
467
+ importanceQuery = importanceQuery.in('memory_type', opts.memoryTypes);
468
+ }
469
+ if (opts.relatedUser) {
470
+ importanceQuery = importanceQuery.eq('related_user', opts.relatedUser);
471
+ }
472
+ if (opts.relatedWallet) {
473
+ importanceQuery = importanceQuery.eq('related_wallet', opts.relatedWallet);
474
+ }
475
+ if (opts.minImportance) {
476
+ importanceQuery = importanceQuery.gte('importance', opts.minImportance);
477
+ }
478
+ if (opts.tags && opts.tags.length > 0) {
479
+ importanceQuery = importanceQuery.overlaps('tags', opts.tags);
480
+ }
481
+ // Text search: find memories whose summary/tags contain query keywords
482
+ const textSearchPromise = (opts.query && opts.query.length > 3) ? (async () => {
483
+ try {
484
+ // Extract meaningful keywords (skip short/common words)
485
+ const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'how', 'what', 'who', 'why', 'when', 'where', 'does', 'do', 'did', 'can', 'will', 'about', 'that', 'this', 'it']);
486
+ const keywords = opts.query.toLowerCase().split(/\s+/)
487
+ .filter(w => w.length > 2 && !stopWords.has(w))
488
+ .slice(0, 4);
489
+ if (keywords.length === 0)
490
+ return [];
491
+ // Search summary with ilike for each keyword
492
+ let textQuery = db
493
+ .from('memories')
494
+ .select('*')
495
+ .gte('decay_factor', minDecay)
496
+ .not('source', 'in', '("demo","demo-maas")')
497
+ .or(keywords.map(k => `summary.ilike.%${k}%`).join(','))
498
+ .order('importance', { ascending: false })
499
+ .limit(limit * 2);
500
+ textQuery = scopeToOwner(textQuery);
501
+ if (opts.memoryTypes && opts.memoryTypes.length > 0) {
502
+ textQuery = textQuery.in('memory_type', opts.memoryTypes);
503
+ }
504
+ const { data: textData } = await textQuery;
505
+ return textData || [];
506
+ }
507
+ catch {
508
+ return [];
509
+ }
510
+ })() : Promise.resolve([]);
511
+ // Phase 2b: Always fetch knowledge-seed memories (small fixed set, ~20 rows)
512
+ // These are curated factual memories that must compete in scoring regardless of vector pool
513
+ let knowledgeSeedQuery = db
514
+ .from('memories')
515
+ .select('*')
516
+ .eq('source', 'knowledge-seed')
517
+ .gte('decay_factor', minDecay);
518
+ knowledgeSeedQuery = scopeToOwner(knowledgeSeedQuery);
519
+ if (opts.memoryTypes && opts.memoryTypes.length > 0) {
520
+ knowledgeSeedQuery = knowledgeSeedQuery.in('memory_type', opts.memoryTypes);
521
+ }
522
+ const [importanceResult, textResults, knowledgeSeeds] = await Promise.all([
523
+ importanceQuery,
524
+ textSearchPromise,
525
+ (async () => { try {
526
+ const r = await knowledgeSeedQuery;
527
+ return r.data || [];
528
+ }
529
+ catch {
530
+ return [];
531
+ } })(),
532
+ vectorSearchPromise, // Ensure vector search completes before merge
533
+ ]);
534
+ const { data, error } = importanceResult;
535
+ // Merge text search results + knowledge seeds into data
536
+ if (data) {
537
+ const existingIds = new Set(data.map((m) => m.id));
538
+ if (Array.isArray(textResults) && textResults.length > 0) {
539
+ for (const m of textResults) {
540
+ if (!existingIds.has(m.id)) {
541
+ data.push(m);
542
+ existingIds.add(m.id);
543
+ }
544
+ }
545
+ log.debug({ textHits: textResults.length }, 'Text search added candidates');
546
+ }
547
+ if (Array.isArray(knowledgeSeeds) && knowledgeSeeds.length > 0) {
548
+ let seedsAdded = 0;
549
+ for (const m of knowledgeSeeds) {
550
+ if (!existingIds.has(m.id)) {
551
+ data.push(m);
552
+ existingIds.add(m.id);
553
+ seedsAdded++;
554
+ }
555
+ // Compute vector similarity for seeds that lack it (fetched via metadata, not vector search)
556
+ const rawEmb = m.embedding;
557
+ if (primaryQueryEmbedding && rawEmb && !vectorScores.has(m.id)) {
558
+ // Supabase returns embeddings as JSON strings from select('*')
559
+ const emb = typeof rawEmb === 'string' ? JSON.parse(rawEmb) : rawEmb;
560
+ const qEmb = primaryQueryEmbedding;
561
+ if (emb.length === qEmb.length) {
562
+ let dot = 0, magA = 0, magB = 0;
563
+ for (let i = 0; i < emb.length; i++) {
564
+ dot += qEmb[i] * emb[i];
565
+ magA += qEmb[i] * qEmb[i];
566
+ magB += emb[i] * emb[i];
567
+ }
568
+ const sim = (magA > 0 && magB > 0) ? dot / (Math.sqrt(magA) * Math.sqrt(magB)) : 0;
569
+ if (sim > 0)
570
+ vectorScores.set(m.id, sim);
571
+ }
572
+ }
573
+ }
574
+ if (seedsAdded > 0)
575
+ log.debug({ seedsAdded, seedsTotal: knowledgeSeeds.length }, 'Knowledge seeds added to candidates');
576
+ }
577
+ }
578
+ if (error) {
579
+ log.error({ error: error.message }, 'Memory recall query failed');
580
+ return [];
581
+ }
582
+ // Phase 3: Merge vector candidates with metadata candidates
583
+ let candidates = (0, encryption_1.decryptMemoryBatch)(data || []);
584
+ // If vector search found memories not in the metadata set, fetch them
585
+ if (vectorScores.size > 0) {
586
+ const metadataIds = new Set(candidates.map(m => m.id));
587
+ const missingIds = [...vectorScores.keys()].filter(id => !metadataIds.has(id));
588
+ if (missingIds.length > 0) {
589
+ let vectorQuery = db
590
+ .from('memories')
591
+ .select('*')
592
+ .in('id', missingIds);
593
+ vectorQuery = scopeToOwner(vectorQuery);
594
+ // Respect memoryTypes filter even for vector-matched results
595
+ if (opts.memoryTypes && opts.memoryTypes.length > 0) {
596
+ vectorQuery = vectorQuery.in('memory_type', opts.memoryTypes);
597
+ }
598
+ const { data: vectorOnly } = await vectorQuery;
599
+ if (vectorOnly)
600
+ candidates = [...candidates, ...(0, encryption_1.decryptMemoryBatch)(vectorOnly)];
601
+ }
602
+ }
603
+ if (candidates.length === 0)
604
+ return [];
605
+ // Phase 4: Score and rank with enhanced composite formula
606
+ const scoredOpts = vectorScores.size > 0 ? { ...opts, _vectorScores: vectorScores } : opts;
607
+ const scored = candidates.map((mem) => ({
608
+ ...mem,
609
+ _score: scoreMemory(mem, scoredOpts),
610
+ }));
611
+ scored.sort((a, b) => b._score - a._score);
612
+ let results = scored.slice(0, limit);
613
+ // Phase 5: Entity-aware recall — find memories via entity graph + co-occurrence
614
+ if (opts.query && results.length > 0) {
615
+ try {
616
+ const entities = await (0, memory_graph_1.findSimilarEntities)(opts.query, { limit: 3 });
617
+ if (entities.length > 0) {
618
+ const resultIdSet = new Set(results.map((m) => m.id));
619
+ // Phase 5a: Direct entity memories
620
+ for (const entity of entities) {
621
+ const entityMemories = await (0, memory_graph_1.getMemoriesByEntity)(entity.id, {
622
+ limit: Math.ceil(limit / 2),
623
+ memoryTypes: opts.memoryTypes,
624
+ });
625
+ for (const mem of entityMemories) {
626
+ if (!resultIdSet.has(mem.id)) {
627
+ results.push({
628
+ ...mem,
629
+ _score: scoreMemory(mem, scoredOpts) + utils_1.RETRIEVAL_WEIGHT_GRAPH * 0.6,
630
+ });
631
+ resultIdSet.add(mem.id);
632
+ }
633
+ }
634
+ }
635
+ log.debug({ entities: entities.map(e => e.name) }, 'Entity-aware recall applied');
636
+ // Phase 5b: Co-occurring entity memories
637
+ let cooccurrenceAdded = 0;
638
+ const cooccurrenceNames = [];
639
+ for (const entity of entities) {
640
+ const cooccurrences = await (0, memory_graph_1.getEntityCooccurrences)(entity.id, { minCooccurrence: 2, maxResults: 3 });
641
+ for (const cooc of cooccurrences) {
642
+ if (cooccurrenceAdded >= limit)
643
+ break;
644
+ const coMems = await (0, memory_graph_1.getMemoriesByEntity)(cooc.related_entity_id, {
645
+ limit: 3,
646
+ memoryTypes: opts.memoryTypes,
647
+ });
648
+ for (const mem of coMems) {
649
+ if (cooccurrenceAdded >= limit)
650
+ break;
651
+ if (!resultIdSet.has(mem.id)) {
652
+ const normalizedStrength = Math.min(cooc.cooccurrence_count / 5, 1);
653
+ results.push({
654
+ ...mem,
655
+ _score: scoreMemory(mem, scoredOpts) + utils_1.RETRIEVAL_WEIGHT_GRAPH * utils_1.RETRIEVAL_WEIGHT_COOCCURRENCE * normalizedStrength,
656
+ });
657
+ resultIdSet.add(mem.id);
658
+ cooccurrenceAdded++;
659
+ }
660
+ }
661
+ }
662
+ if (cooccurrences.length > 0) {
663
+ cooccurrenceNames.push(...cooccurrences.map(c => String(c.related_entity_id)));
664
+ }
665
+ }
666
+ if (cooccurrenceAdded > 0) {
667
+ log.debug({ cooccurrenceAdded, cooccurrenceEntities: cooccurrenceNames.length }, 'Entity co-occurrence recall applied');
668
+ }
669
+ }
670
+ }
671
+ catch (err) {
672
+ log.debug({ err }, 'Entity-aware recall skipped');
673
+ }
674
+ }
675
+ // Phase 6: Bond-typed graph traversal — follow strong bonds first
676
+ // Bond weight multipliers: causal/supports > elaborates > relates > follows
677
+ const BOND_TYPE_WEIGHTS = {
678
+ causes: 1.0,
679
+ supports: 0.9,
680
+ resolves: 0.8,
681
+ elaborates: 0.7,
682
+ contradicts: 0.6,
683
+ relates: 0.4,
684
+ follows: 0.3,
685
+ };
686
+ if (results.length > 0) {
687
+ try {
688
+ const seedIds = results.map((m) => m.id);
689
+ const { data: linked } = await db.rpc('get_linked_memories', {
690
+ seed_ids: seedIds,
691
+ min_strength: 0.2,
692
+ max_results: limit,
693
+ filter_owner: getOwnerWallet() || null,
694
+ });
695
+ if (linked && linked.length > 0) {
696
+ const resultIdSet = new Set(seedIds);
697
+ const graphCandidateIds = linked
698
+ .filter((l) => !resultIdSet.has(l.memory_id))
699
+ .map((l) => l.memory_id);
700
+ if (graphCandidateIds.length > 0) {
701
+ let graphQuery = db
702
+ .from('memories')
703
+ .select('*')
704
+ .in('id', graphCandidateIds);
705
+ graphQuery = scopeToOwner(graphQuery);
706
+ const { data: graphMemories } = await graphQuery;
707
+ if (graphMemories && graphMemories.length > 0) {
708
+ (0, encryption_1.decryptMemoryBatch)(graphMemories);
709
+ // Build link map with bond-type-weighted strength
710
+ const linkBoostMap = new Map();
711
+ for (const l of linked) {
712
+ const bondWeight = BOND_TYPE_WEIGHTS[l.link_type] ?? 0.4;
713
+ const weightedStrength = (l.strength || 0.5) * bondWeight;
714
+ const current = linkBoostMap.get(l.memory_id) || 0;
715
+ linkBoostMap.set(l.memory_id, Math.max(current, weightedStrength));
716
+ }
717
+ const graphScored = graphMemories.map(mem => ({
718
+ ...mem,
719
+ _score: scoreMemory(mem, scoredOpts) + utils_1.RETRIEVAL_WEIGHT_GRAPH * (linkBoostMap.get(mem.id) || 0),
720
+ }));
721
+ results = [...results, ...graphScored]
722
+ .sort((a, b) => b._score - a._score)
723
+ .slice(0, limit);
724
+ log.debug({
725
+ graphExpanded: graphMemories.length,
726
+ linkedTotal: linked.length,
727
+ bondTypes: [...new Set(linked.map((l) => l.link_type))],
728
+ }, 'Bond-typed graph traversal applied');
729
+ }
730
+ }
731
+ }
732
+ }
733
+ catch (err) {
734
+ log.debug({ err }, 'Graph expansion skipped (RPC unavailable)');
735
+ }
736
+ }
737
+ // Phase 7: Type diversity — ensure results span multiple memory types
738
+ // If all results are one type, pull in top candidates from other types
739
+ if (results.length >= 3) {
740
+ const typeSet = new Set(results.map((m) => m.memory_type));
741
+ if (typeSet.size === 1) {
742
+ const dominantType = [...typeSet][0];
743
+ const otherTypes = ['episodic', 'semantic', 'procedural', 'self_model'].filter(t => t !== dominantType);
744
+ const resultIdSet = new Set(results.map((m) => m.id));
745
+ // Find scored candidates from other types that didn't make the cut
746
+ const diverseCandidates = scored
747
+ .filter((m) => otherTypes.includes(m.memory_type) && !resultIdSet.has(m.id))
748
+ .slice(0, Math.ceil(limit / 3));
749
+ if (diverseCandidates.length > 0) {
750
+ // Replace lowest-scored same-type results with diverse candidates
751
+ const replaceCount = Math.min(diverseCandidates.length, Math.ceil(results.length / 3));
752
+ results = [
753
+ ...results.slice(0, results.length - replaceCount),
754
+ ...diverseCandidates.slice(0, replaceCount),
755
+ ].sort((a, b) => b._score - a._score)
756
+ .slice(0, limit);
757
+ log.debug({ injectedTypes: diverseCandidates.map((m) => m.memory_type) }, 'Type diversity applied');
758
+ }
759
+ }
760
+ }
761
+ // Final owner_wallet guard: strip any memories that don't belong to the current owner
762
+ // This catches leaks from entity/graph/fragment paths that may not filter by owner
763
+ const finalOwner = getOwnerWallet();
764
+ if (finalOwner) {
765
+ const beforeCount = results.length;
766
+ results = results.filter((m) => m.owner_wallet === finalOwner);
767
+ if (results.length < beforeCount) {
768
+ log.warn({ stripped: beforeCount - results.length, owner: finalOwner }, 'Owner guard stripped foreign memories from recall results');
769
+ }
770
+ }
771
+ // Update access counts in parallel (skip for internal processing like dream cycles)
772
+ if (opts.trackAccess !== false) {
773
+ const ids = results.map((m) => m.id);
774
+ updateMemoryAccess(ids).catch(err => log.warn({ err }, 'Memory access tracking failed'));
775
+ // Hebbian: reinforce links between co-retrieved memories
776
+ reinforceCoRetrievedLinks(ids).catch(err => log.debug({ err }, 'Link reinforcement failed'));
777
+ }
778
+ log.debug({
779
+ recalled: results.length,
780
+ topScore: results[0]?._score?.toFixed(3),
781
+ query: opts.query?.slice(0, 40),
782
+ vectorAssisted: vectorScores.size > 0,
783
+ typeSpread: [...new Set(results.map((m) => m.memory_type))].join(','),
784
+ }, 'Memories recalled');
785
+ return results;
786
+ }
787
+ catch (err) {
788
+ log.error({ err }, 'Memory recall failed');
789
+ return [];
790
+ }
791
+ }
792
+ // Stopwords to exclude from keyword matching — common words that cause false positives
793
+ const STOPWORDS = new Set([
794
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
795
+ 'of', 'with', 'by', 'from', 'is', 'it', 'its', 'are', 'was', 'were',
796
+ 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
797
+ 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'shall',
798
+ 'not', 'no', 'nor', 'so', 'if', 'then', 'than', 'that', 'this',
799
+ 'what', 'which', 'who', 'whom', 'how', 'when', 'where', 'why',
800
+ 'all', 'each', 'every', 'both', 'few', 'more', 'most', 'some', 'any',
801
+ 'about', 'into', 'through', 'just', 'also', 'very', 'much', 'like',
802
+ 'get', 'got', 'your', 'you', 'my', 'me', 'his', 'her', 'our', 'their',
803
+ ]);
804
+ /**
805
+ * Check if a word matches within text using word boundary logic.
806
+ * Avoids false positives like "sol" matching "solution".
807
+ */
808
+ function wordBoundaryMatch(word, text) {
809
+ // Escape regex special chars, then wrap with word boundaries
810
+ const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
811
+ const re = new RegExp(`\\b${escaped}\\b`, 'i');
812
+ return re.test(text);
813
+ }
814
+ /**
815
+ * Extract meaningful query terms: lowercase, filter stopwords and short words.
816
+ */
817
+ function extractQueryTerms(query) {
818
+ return query
819
+ .toLowerCase()
820
+ .split(/\s+/)
821
+ .filter(w => w.length > 2 && !STOPWORDS.has(w));
822
+ }
823
+ /**
824
+ * Enhanced scoring function with optional vector similarity component.
825
+ *
826
+ * When vector similarity is available:
827
+ * score = (w_recency * recency + w_relevance * keyword_relevance
828
+ * + w_importance * importance + w_vector * vector_sim) * decay
829
+ *
830
+ * When not available (graceful fallback):
831
+ * score = (w_recency * recency + w_relevance * keyword_relevance
832
+ * + w_importance * importance) * decay
833
+ */
834
+ function scoreMemory(mem, opts) {
835
+ const now = Date.now();
836
+ // Recency: exponential decay from last access (paper: 0.995^hours)
837
+ const hoursSinceAccess = (now - new Date(mem.last_accessed).getTime()) / (1000 * 60 * 60);
838
+ const recency = Math.pow(utils_1.RECENCY_DECAY_BASE, hoursSinceAccess);
839
+ // Text similarity (keyword overlap with word boundaries + stopword filtering)
840
+ let textScore = 0.5;
841
+ if (opts.query) {
842
+ const queryTerms = extractQueryTerms(opts.query);
843
+ if (queryTerms.length > 0) {
844
+ const summaryLower = mem.summary.toLowerCase();
845
+ // Summary matches are worth more than content matches
846
+ let summaryHits = 0;
847
+ let contentHits = 0;
848
+ for (const term of queryTerms) {
849
+ if (wordBoundaryMatch(term, summaryLower)) {
850
+ summaryHits++;
851
+ }
852
+ else if (mem.content && wordBoundaryMatch(term, mem.content.toLowerCase())) {
853
+ contentHits++;
854
+ }
855
+ }
856
+ // Summary matches count full, content matches count half
857
+ const effectiveMatches = summaryHits + contentHits * 0.5;
858
+ textScore = 0.3 + 0.7 * Math.min(effectiveMatches / queryTerms.length, 1);
859
+ }
860
+ }
861
+ // Tag + concept overlap score
862
+ let tagScore = 0.5;
863
+ if (opts.tags && opts.tags.length > 0) {
864
+ const memLabels = [...(mem.tags || []), ...(mem.concepts || [])];
865
+ const overlap = memLabels.filter(t => opts.tags.includes(t)).length;
866
+ tagScore = 0.5 + 0.5 * Math.min(overlap / opts.tags.length, 1);
867
+ }
868
+ // Relevance: average of text and tag similarity
869
+ const relevance = (textScore + tagScore) / 2;
870
+ // Vector similarity component (0 if not available)
871
+ const vectorSim = opts._vectorScores?.get(mem.id) || 0;
872
+ // Additive formula, normalized so vector vs non-vector scores are comparable
873
+ let rawScore = utils_1.RETRIEVAL_WEIGHT_RECENCY * recency +
874
+ utils_1.RETRIEVAL_WEIGHT_RELEVANCE * relevance +
875
+ utils_1.RETRIEVAL_WEIGHT_IMPORTANCE * mem.importance;
876
+ if (vectorSim > 0) {
877
+ rawScore += utils_1.RETRIEVAL_WEIGHT_VECTOR * vectorSim;
878
+ rawScore /= (utils_1.RETRIEVAL_WEIGHT_RECENCY + utils_1.RETRIEVAL_WEIGHT_RELEVANCE + utils_1.RETRIEVAL_WEIGHT_IMPORTANCE + utils_1.RETRIEVAL_WEIGHT_VECTOR);
879
+ }
880
+ else {
881
+ rawScore /= (utils_1.RETRIEVAL_WEIGHT_RECENCY + utils_1.RETRIEVAL_WEIGHT_RELEVANCE + utils_1.RETRIEVAL_WEIGHT_IMPORTANCE);
882
+ }
883
+ // Knowledge type boost: semantic/procedural/self_model rank above raw episodic
884
+ const typeBoost = utils_1.KNOWLEDGE_TYPE_BOOST[mem.memory_type] || 0;
885
+ rawScore += typeBoost;
886
+ // Knowledge-seed memories get boosted ONLY when vector-relevant to the query
887
+ // High vector sim = strong boost; no vector match = no boost (don't pollute unrelated queries)
888
+ if (mem.source === 'knowledge-seed' && vectorSim > 0.25) {
889
+ rawScore += 2.0 + vectorSim * 2.0; // ranges from +2.5 (sim=0.25) to +4.0 (sim=1.0)
890
+ }
891
+ else if (mem.source === 'knowledge-seed') {
892
+ rawScore += 0.5; // moderate boost for seeds without vector match
893
+ }
894
+ // Consolidation memories are dream-cycle meta-observations — strong penalty
895
+ // 2000+ consolidation memories flood results; only the most vector-relevant should surface
896
+ if (mem.source === 'consolidation') {
897
+ rawScore *= vectorSim > 0.5 ? 0.45 : 0.30;
898
+ }
899
+ return rawScore * mem.decay_factor;
900
+ }
901
+ // ---- PROGRESSIVE DISCLOSURE ---- //
902
+ /**
903
+ * Lightweight recall that returns only summaries (~50 tokens each).
904
+ * Use for dream cycle focal point generation, overview scans, and
905
+ * anywhere full content isn't needed. 10x more token-efficient than full recall.
906
+ *
907
+ * Call hydrateMemories() to fetch full content for selected IDs.
908
+ */
909
+ async function recallMemorySummaries(opts) {
910
+ const db = (0, database_1.getDb)();
911
+ const limit = opts.limit || 10;
912
+ const minDecay = opts.minDecay ?? 0.1;
913
+ try {
914
+ let query = db
915
+ .from('memories')
916
+ .select('id, memory_type, summary, tags, concepts, importance, decay_factor, created_at, source')
917
+ .gte('decay_factor', minDecay)
918
+ .not('source', 'in', '("demo","demo-maas")')
919
+ .order('importance', { ascending: false })
920
+ .order('created_at', { ascending: false })
921
+ .limit(limit);
922
+ query = scopeToOwner(query);
923
+ if (opts.memoryTypes && opts.memoryTypes.length > 0) {
924
+ query = query.in('memory_type', opts.memoryTypes);
925
+ }
926
+ if (opts.relatedUser) {
927
+ query = query.eq('related_user', opts.relatedUser);
928
+ }
929
+ if (opts.relatedWallet) {
930
+ query = query.eq('related_wallet', opts.relatedWallet);
931
+ }
932
+ if (opts.minImportance) {
933
+ query = query.gte('importance', opts.minImportance);
934
+ }
935
+ if (opts.tags && opts.tags.length > 0) {
936
+ query = query.overlaps('tags', opts.tags);
937
+ }
938
+ const { data, error } = await query;
939
+ if (error) {
940
+ log.error({ error: error.message }, 'Memory summary recall failed');
941
+ return [];
942
+ }
943
+ return (data || []);
944
+ }
945
+ catch (err) {
946
+ log.error({ err }, 'Memory summary recall failed');
947
+ return [];
948
+ }
949
+ }
950
+ /**
951
+ * Fetch full memory content for specific IDs (second stage of progressive disclosure).
952
+ * Use after recallMemorySummaries() to hydrate only the memories you actually need.
953
+ */
954
+ async function hydrateMemories(ids) {
955
+ if (ids.length === 0)
956
+ return [];
957
+ const db = (0, database_1.getDb)();
958
+ try {
959
+ let query = db
960
+ .from('memories')
961
+ .select('*')
962
+ .in('id', ids);
963
+ query = scopeToOwner(query);
964
+ const { data, error } = await query;
965
+ if (error) {
966
+ log.error({ error: error.message }, 'Memory hydration failed');
967
+ return [];
968
+ }
969
+ return (0, encryption_1.decryptMemoryBatch)((data || []));
970
+ }
971
+ catch (err) {
972
+ log.error({ err }, 'Memory hydration failed');
973
+ return [];
974
+ }
975
+ }
976
+ // ---- ACCESS TRACKING ---- //
977
+ async function updateMemoryAccess(ids) {
978
+ if (ids.length === 0)
979
+ return;
980
+ const db = (0, database_1.getDb)();
981
+ // Single batch query: increment access_count, refresh last_accessed, boost decay
982
+ const { error } = await db.rpc('batch_boost_memory_access', { memory_ids: ids });
983
+ if (error) {
984
+ log.warn({ error: error.message, ids }, 'Batch memory access update failed');
985
+ }
986
+ // Importance re-scoring: memories retrieved often become more important over time.
987
+ // Small boost per retrieval, capped at 1.0. Mirrors "rehearsal effect" in human memory.
988
+ try {
989
+ for (const id of ids) {
990
+ await db.rpc('boost_memory_importance', {
991
+ memory_id: id,
992
+ boost_amount: 0.02, // +2% per retrieval
993
+ max_importance: 1.0,
994
+ });
995
+ }
996
+ }
997
+ catch (err) {
998
+ // Non-critical — RPC may not exist yet, will be created in next migration
999
+ log.debug({ err }, 'Importance re-scoring skipped (RPC may not exist)');
1000
+ }
1001
+ }
1002
+ // ---- ASSOCIATION GRAPH ---- //
1003
+ /**
1004
+ * Create a typed, weighted link between two memories.
1005
+ * Idempotent — upserts on (source_id, target_id, link_type).
1006
+ */
1007
+ async function createMemoryLink(sourceId, targetId, linkType, strength = 0.5) {
1008
+ if (sourceId === targetId)
1009
+ return;
1010
+ const db = (0, database_1.getDb)();
1011
+ const { error } = await db
1012
+ .from('memory_links')
1013
+ .upsert({
1014
+ source_id: sourceId,
1015
+ target_id: targetId,
1016
+ link_type: linkType,
1017
+ strength: (0, utils_1.clamp)(strength, 0, 1),
1018
+ }, { onConflict: 'source_id,target_id,link_type' });
1019
+ if (error) {
1020
+ log.debug({ error: error.message, sourceId, targetId, linkType }, 'Link creation failed');
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Auto-link a new memory to related existing memories.
1025
+ * Uses vector similarity, concept overlap, and user overlap to find candidates.
1026
+ * Classifies link type via lightweight heuristics.
1027
+ */
1028
+ async function autoLinkMemory(memoryId, opts) {
1029
+ const db = (0, database_1.getDb)();
1030
+ // 1. Link evidence_ids as 'supports' links
1031
+ if (opts.evidenceIds && opts.evidenceIds.length > 0) {
1032
+ for (const evidenceId of opts.evidenceIds) {
1033
+ await createMemoryLink(memoryId, evidenceId, 'supports', 0.8);
1034
+ }
1035
+ }
1036
+ // 2. Find candidates via vector similarity (if embeddings available)
1037
+ const candidates = [];
1038
+ if ((0, embeddings_1.isEmbeddingEnabled)()) {
1039
+ const embedding = await (0, embeddings_1.generateEmbedding)(opts.summary);
1040
+ if (embedding) {
1041
+ const { data: similar } = await db.rpc('match_memories', {
1042
+ query_embedding: JSON.stringify(embedding),
1043
+ match_threshold: utils_1.LINK_SIMILARITY_THRESHOLD,
1044
+ match_count: utils_1.MAX_AUTO_LINKS * 2,
1045
+ filter_owner: getOwnerWallet() || null,
1046
+ });
1047
+ if (similar) {
1048
+ // Fetch metadata for link classification
1049
+ const similarIds = similar.map((s) => s.id).filter((id) => id !== memoryId);
1050
+ if (similarIds.length > 0) {
1051
+ let metaQuery = db
1052
+ .from('memories')
1053
+ .select('id, memory_type, concepts, related_user, emotional_valence, created_at')
1054
+ .in('id', similarIds);
1055
+ metaQuery = scopeToOwner(metaQuery);
1056
+ const { data: metas } = await metaQuery;
1057
+ if (metas) {
1058
+ const simMap = new Map(similar.map((s) => [Number(s.id), Number(s.similarity)]));
1059
+ for (const m of metas) {
1060
+ const mid = Number(m.id);
1061
+ candidates.push({
1062
+ id: mid,
1063
+ similarity: simMap.get(mid) || 0,
1064
+ memory_type: String(m.memory_type),
1065
+ concepts: (m.concepts || []),
1066
+ related_user: m.related_user ? String(m.related_user) : null,
1067
+ emotional_valence: Number(m.emotional_valence || 0),
1068
+ created_at: String(m.created_at),
1069
+ });
1070
+ }
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ }
1076
+ // 3. Also find by concept overlap (fallback when no embeddings)
1077
+ if (candidates.length < utils_1.MAX_AUTO_LINKS && opts.concepts && opts.concepts.length > 0) {
1078
+ let conceptQuery = db
1079
+ .from('memories')
1080
+ .select('id, memory_type, concepts, related_user, emotional_valence, created_at')
1081
+ .overlaps('concepts', opts.concepts)
1082
+ .neq('id', memoryId)
1083
+ .order('created_at', { ascending: false })
1084
+ .limit(utils_1.MAX_AUTO_LINKS);
1085
+ conceptQuery = scopeToOwner(conceptQuery);
1086
+ const { data: conceptMatches } = await conceptQuery;
1087
+ if (conceptMatches) {
1088
+ const existingIds = new Set(candidates.map(c => c.id));
1089
+ for (const m of conceptMatches) {
1090
+ if (!existingIds.has(m.id)) {
1091
+ candidates.push({ ...m, similarity: 0.4 });
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ // 4. Classify link types and create links (limit to MAX_AUTO_LINKS)
1097
+ const concepts = opts.concepts || [];
1098
+ let linksCreated = 0;
1099
+ for (const candidate of candidates.slice(0, utils_1.MAX_AUTO_LINKS)) {
1100
+ if (candidate.id === memoryId)
1101
+ continue;
1102
+ const linkType = classifyLinkType(opts, candidate, concepts);
1103
+ const strength = candidate.similarity > 0 ? (0, utils_1.clamp)(candidate.similarity, 0.3, 0.9) : 0.5;
1104
+ await createMemoryLink(memoryId, candidate.id, linkType, strength);
1105
+ linksCreated++;
1106
+ }
1107
+ if (linksCreated > 0) {
1108
+ log.debug({ memoryId, linksCreated }, 'Auto-linked memory');
1109
+ }
1110
+ }
1111
+ /**
1112
+ * Classify the relationship type between a new memory and an existing candidate.
1113
+ */
1114
+ function classifyLinkType(newMem, candidate, newConcepts) {
1115
+ const sameUser = newMem.relatedUser && newMem.relatedUser === candidate.related_user;
1116
+ const recentCandidate = (Date.now() - new Date(candidate.created_at).getTime()) < 6 * 60 * 60 * 1000; // within 6h
1117
+ const conceptOverlap = (candidate.concepts || []).filter(c => newConcepts.includes(c)).length;
1118
+ const valenceFlip = Math.abs((newMem.emotionalValence || 0) - candidate.emotional_valence) > 1.0;
1119
+ // Same user + recent = temporal sequence
1120
+ if (sameUser && recentCandidate)
1121
+ return 'follows';
1122
+ // Large emotional valence difference = potential contradiction
1123
+ if (valenceFlip && conceptOverlap > 0)
1124
+ return 'contradicts';
1125
+ // Semantic memory building on episodic = elaboration
1126
+ if (newMem.type === 'semantic' && candidate.memory_type === 'episodic')
1127
+ return 'elaborates';
1128
+ // High concept overlap = related
1129
+ if (conceptOverlap >= 2)
1130
+ return 'relates';
1131
+ return 'relates';
1132
+ }
1133
+ /**
1134
+ * Hebbian reinforcement: boost link strength between co-retrieved memories.
1135
+ * "Memories that fire together wire together."
1136
+ */
1137
+ async function reinforceCoRetrievedLinks(ids) {
1138
+ if (ids.length < 2)
1139
+ return;
1140
+ const db = (0, database_1.getDb)();
1141
+ const { data, error } = await db.rpc('boost_link_strength', {
1142
+ memory_ids: ids,
1143
+ boost_amount: utils_1.LINK_CO_RETRIEVAL_BOOST,
1144
+ });
1145
+ if (error) {
1146
+ log.debug({ error: error.message }, 'Link reinforcement RPC failed');
1147
+ }
1148
+ else if (data && data > 0) {
1149
+ log.debug({ boosted: data }, 'Co-retrieval link reinforcement applied');
1150
+ }
1151
+ }
1152
+ // ---- ENTITY EXTRACTION ---- //
1153
+ /**
1154
+ * Extract entities from a stored memory and link them to the knowledge graph.
1155
+ * Called as fire-and-forget after storeMemory().
1156
+ */
1157
+ async function extractAndLinkEntitiesForMemory(memoryId, opts) {
1158
+ try {
1159
+ await (0, memory_graph_1.extractAndLinkEntities)(memoryId, opts.content, opts.summary, opts.relatedUser);
1160
+ }
1161
+ catch (err) {
1162
+ log.debug({ err, memoryId }, 'Entity extraction failed');
1163
+ }
1164
+ }
1165
+ // ---- DECAY ---- //
1166
+ /**
1167
+ * Apply type-specific memory decay.
1168
+ * Episodic memories fade fastest (0.93/day), self-model slowest (0.99/day).
1169
+ * This mirrors human cognition: events are forgotten but identity persists.
1170
+ */
1171
+ async function decayMemories() {
1172
+ const db = (0, database_1.getDb)();
1173
+ const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
1174
+ try {
1175
+ let totalDecayed = 0;
1176
+ // Batch decay per memory type (4 queries instead of N)
1177
+ for (const [memType, rate] of Object.entries(utils_1.DECAY_RATES)) {
1178
+ const { data, error } = await db.rpc('batch_decay_memories', {
1179
+ decay_type: memType,
1180
+ decay_rate: rate,
1181
+ min_decay: utils_1.MEMORY_MIN_DECAY,
1182
+ cutoff,
1183
+ });
1184
+ if (error) {
1185
+ log.warn({ error: error.message, memType }, 'Batch decay failed for type');
1186
+ continue;
1187
+ }
1188
+ totalDecayed += data || 0;
1189
+ }
1190
+ if (totalDecayed > 0) {
1191
+ log.info({ decayed: totalDecayed }, 'Type-specific memory decay applied');
1192
+ }
1193
+ return totalDecayed;
1194
+ }
1195
+ catch (err) {
1196
+ log.error({ err }, 'Memory decay failed');
1197
+ return 0;
1198
+ }
1199
+ }
1200
+ async function getMemoryStats() {
1201
+ const db = (0, database_1.getDb)();
1202
+ const stats = {
1203
+ total: 0,
1204
+ byType: { episodic: 0, semantic: 0, procedural: 0, self_model: 0, introspective: 0 },
1205
+ avgImportance: 0,
1206
+ avgDecay: 0,
1207
+ oldestMemory: null,
1208
+ newestMemory: null,
1209
+ totalDreamSessions: 0,
1210
+ uniqueUsers: 0,
1211
+ topTags: [],
1212
+ topConcepts: [],
1213
+ embeddedCount: 0,
1214
+ };
1215
+ try {
1216
+ let statsQuery = db
1217
+ .from('memories')
1218
+ .select('memory_type, importance, decay_factor, created_at, related_user, tags, concepts, embedding')
1219
+ .gt('decay_factor', utils_1.MEMORY_MIN_DECAY);
1220
+ statsQuery = scopeToOwner(statsQuery);
1221
+ const { data: memories } = await statsQuery;
1222
+ if (memories && memories.length > 0) {
1223
+ stats.total = memories.length;
1224
+ let impSum = 0;
1225
+ let decaySum = 0;
1226
+ const tagCounts = {};
1227
+ const conceptCounts = {};
1228
+ const users = new Set();
1229
+ for (const m of memories) {
1230
+ const type = m.memory_type;
1231
+ if (type in stats.byType)
1232
+ stats.byType[type]++;
1233
+ impSum += m.importance;
1234
+ decaySum += m.decay_factor;
1235
+ if (m.related_user)
1236
+ users.add(m.related_user);
1237
+ if (m.embedding)
1238
+ stats.embeddedCount++;
1239
+ if (m.tags) {
1240
+ for (const tag of m.tags) {
1241
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
1242
+ }
1243
+ }
1244
+ if (m.concepts) {
1245
+ for (const concept of m.concepts) {
1246
+ conceptCounts[concept] = (conceptCounts[concept] || 0) + 1;
1247
+ }
1248
+ }
1249
+ }
1250
+ stats.avgImportance = impSum / memories.length;
1251
+ stats.avgDecay = decaySum / memories.length;
1252
+ stats.uniqueUsers = users.size;
1253
+ stats.topTags = Object.entries(tagCounts)
1254
+ .sort((a, b) => b[1] - a[1])
1255
+ .slice(0, 10)
1256
+ .map(([tag, count]) => ({ tag, count }));
1257
+ stats.topConcepts = Object.entries(conceptCounts)
1258
+ .sort((a, b) => b[1] - a[1])
1259
+ .slice(0, 10)
1260
+ .map(([concept, count]) => ({ concept, count }));
1261
+ const sorted = memories.map(m => m.created_at).sort();
1262
+ stats.oldestMemory = sorted[0] || null;
1263
+ stats.newestMemory = sorted[sorted.length - 1] || null;
1264
+ }
1265
+ const { count, error: dreamError } = await db
1266
+ .from('dream_logs')
1267
+ .select('id', { count: 'exact', head: true });
1268
+ if (dreamError) {
1269
+ log.warn({ error: dreamError.message }, 'Failed to count dream logs');
1270
+ }
1271
+ stats.totalDreamSessions = count || 0;
1272
+ }
1273
+ catch (err) {
1274
+ log.error({ err }, 'Failed to get memory stats');
1275
+ }
1276
+ return stats;
1277
+ }
1278
+ // ---- RECENT MEMORIES ---- //
1279
+ async function getRecentMemories(hours, types, limit) {
1280
+ const db = (0, database_1.getDb)();
1281
+ const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
1282
+ let query = db
1283
+ .from('memories')
1284
+ .select('*')
1285
+ .gte('created_at', since)
1286
+ .order('created_at', { ascending: false })
1287
+ .limit(limit || 50);
1288
+ query = scopeToOwner(query);
1289
+ if (types && types.length > 0) {
1290
+ query = query.in('memory_type', types);
1291
+ }
1292
+ const { data, error } = await query;
1293
+ if (error) {
1294
+ log.error({ error: error.message }, 'Failed to get recent memories');
1295
+ return [];
1296
+ }
1297
+ return (0, encryption_1.decryptMemoryBatch)(data || []);
1298
+ }
1299
+ // ---- SELF-MODEL ---- //
1300
+ async function getSelfModel() {
1301
+ const db = (0, database_1.getDb)();
1302
+ let query = db
1303
+ .from('memories')
1304
+ .select('*')
1305
+ .eq('memory_type', 'self_model')
1306
+ .gt('decay_factor', 0.2)
1307
+ .order('importance', { ascending: false })
1308
+ .order('created_at', { ascending: false })
1309
+ .limit(5);
1310
+ query = scopeToOwner(query);
1311
+ const { data, error } = await query;
1312
+ if (error) {
1313
+ log.error({ error: error.message }, 'Failed to get self model');
1314
+ return [];
1315
+ }
1316
+ return (0, encryption_1.decryptMemoryBatch)(data || []);
1317
+ }
1318
+ // ---- STORE DREAM LOG ---- //
1319
+ async function storeDreamLog(sessionType, inputMemoryIds, output, newMemoryIds) {
1320
+ const db = (0, database_1.getDb)();
1321
+ const { error } = await db
1322
+ .from('dream_logs')
1323
+ .insert({
1324
+ session_type: sessionType,
1325
+ input_memory_ids: inputMemoryIds,
1326
+ output: output.slice(0, utils_1.MEMORY_MAX_CONTENT_LENGTH),
1327
+ new_memories_created: newMemoryIds,
1328
+ });
1329
+ if (error) {
1330
+ log.error({ error: error.message }, 'Failed to store dream log');
1331
+ }
1332
+ }
1333
+ // ---- HELPERS ---- //
1334
+ function formatMemoryContext(memories) {
1335
+ if (memories.length === 0)
1336
+ return '';
1337
+ const lines = ['## Memory Recall'];
1338
+ const episodic = memories.filter(m => m.memory_type === 'episodic');
1339
+ const semantic = memories.filter(m => m.memory_type === 'semantic');
1340
+ const procedural = memories.filter(m => m.memory_type === 'procedural');
1341
+ const selfModel = memories.filter(m => m.memory_type === 'self_model');
1342
+ const introspective = memories.filter(m => m.memory_type === 'introspective');
1343
+ if (episodic.length > 0) {
1344
+ lines.push('### Past Interactions');
1345
+ for (const m of episodic) {
1346
+ lines.push(`- [${(0, utils_1.timeAgo)(m.created_at)}] ${m.summary}`);
1347
+ }
1348
+ }
1349
+ if (semantic.length > 0) {
1350
+ lines.push('### Things You Know');
1351
+ for (const m of semantic) {
1352
+ lines.push(`- ${m.summary}`);
1353
+ }
1354
+ }
1355
+ if (procedural.length > 0) {
1356
+ lines.push('### Learned Strategies (from past outcomes)');
1357
+ for (const m of procedural) {
1358
+ const meta = m.metadata;
1359
+ const confidence = meta?.positiveRate != null
1360
+ ? ` [${Math.round(meta.positiveRate * 100)}% success rate, based on ${meta.basedOn || '?'} interactions]`
1361
+ : '';
1362
+ lines.push(`- ${m.summary}${confidence}`);
1363
+ }
1364
+ }
1365
+ if (introspective.length > 0) {
1366
+ lines.push('### Your Own Reflections');
1367
+ for (const m of introspective) {
1368
+ lines.push(`- [${(0, utils_1.timeAgo)(m.created_at)}] ${m.summary}`);
1369
+ }
1370
+ }
1371
+ if (selfModel.length > 0) {
1372
+ lines.push('### Self-Observations');
1373
+ for (const m of selfModel) {
1374
+ lines.push(`- ${m.summary}`);
1375
+ }
1376
+ }
1377
+ lines.push('');
1378
+ lines.push('You REMEMBER these interactions and facts. Reference them naturally if relevant.');
1379
+ if (procedural.length > 0) {
1380
+ lines.push('');
1381
+ lines.push('IMPORTANT: You MUST follow the Learned Strategies above. They are behavioral rules you derived from analyzing your own past successes and failures. Apply them to this response.');
1382
+ }
1383
+ return lines.join('\n');
1384
+ }
1385
+ function calculateImportance(opts) {
1386
+ let score = 0.4;
1387
+ if (opts.tier === 'WHALE')
1388
+ score += 0.3;
1389
+ else if (opts.tier === 'SMALL')
1390
+ score += 0.1;
1391
+ else if (opts.tier === 'SELLER')
1392
+ score += 0.2;
1393
+ if (opts.feature === 'question')
1394
+ score += 0.15;
1395
+ if (opts.mood === 'PUMPING' || opts.mood === 'DUMPING')
1396
+ score += 0.1;
1397
+ if (opts.mood === 'NEW_ATH' || opts.mood === 'WHALE_SELL')
1398
+ score += 0.15;
1399
+ if (opts.isFirstInteraction)
1400
+ score += 0.1;
1401
+ return (0, utils_1.clamp)(score, 0, 1);
1402
+ }
1403
+ /**
1404
+ * Score importance using LLM (Park et al. 2023).
1405
+ * Falls back to rule-based calculateImportance() on failure.
1406
+ */
1407
+ async function scoreImportanceWithLLM(description, fallbackOpts) {
1408
+ try {
1409
+ const response = await (0, claude_client_1.generateImportanceScore)(description);
1410
+ const parsed = parseInt(response.trim(), 10);
1411
+ if (!isNaN(parsed) && parsed >= 1 && parsed <= 10) {
1412
+ return parsed / 10;
1413
+ }
1414
+ log.warn({ response }, 'LLM importance score unparseable, using fallback');
1415
+ return calculateImportance(fallbackOpts || {});
1416
+ }
1417
+ catch (err) {
1418
+ log.warn({ err }, 'LLM importance scoring failed, using fallback');
1419
+ return calculateImportance(fallbackOpts || {});
1420
+ }
1421
+ }
1422
+ function moodToValence(mood) {
1423
+ switch (mood) {
1424
+ case 'PUMPING': return 0.3;
1425
+ case 'NEW_ATH': return 0.5;
1426
+ case 'DUMPING': return -0.4;
1427
+ case 'WHALE_SELL': return -0.6;
1428
+ case 'SIDEWAYS': return -0.1;
1429
+ default: return 0;
1430
+ }
1431
+ }
1432
+ //# sourceMappingURL=memory.js.map