@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
package/src/db/memory.ts CHANGED
@@ -1,23 +1,27 @@
1
+ import type { Context } from 'effect'
2
+ import { Effect } from 'effect'
1
3
  import type { z } from 'zod'
2
4
 
3
5
  import { aiLogger } from '../config/logger'
4
- import type { CreateHelperAgentFn } from '../runtime/helper-model'
5
- import { createHelperModelRuntime } from '../runtime/helper-model'
6
- import { formatResults } from '../runtime/memory-format'
6
+ import type { CreateHelperAgentFn, HelperModelRuntime } from '../runtime/helper-model'
7
+ import { formatResults } from '../runtime/memory/memory-format'
7
8
  import {
8
9
  buildMemoryFactMaps,
9
10
  compileMemoryUpdatesFromDelta,
10
11
  createMemoryActionPlan,
11
12
  postProcessMemoryFacts,
12
13
  projectMemoryDeltaToScope,
13
- } from '../runtime/memory-pipeline'
14
- import { getFactRetrievalMessages } from '../runtime/memory-prompts-fact'
15
- import { parseMessages } from '../runtime/memory-prompts-parse'
16
- import { getClassifyMemoryDeltaPrompt } from '../runtime/memory-prompts-update'
17
- import { getRuntimeConfig } from '../runtime/runtime-config'
14
+ } from '../runtime/memory/memory-pipeline'
15
+ import { getFactRetrievalMessages } from '../runtime/memory/memory-prompts-fact'
16
+ import { parseMessages } from '../runtime/memory/memory-prompts-parse'
17
+ import { getClassifyMemoryDeltaPrompt } from '../runtime/memory/memory-prompts-update'
18
+ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
19
+ import type { BackgroundWorkService } from '../services/background-work.service'
20
+ import { MemoryServiceError, tryMemoryPromise } from '../services/memory/memory-errors'
21
+ import { sha256Hex } from '../utils/crypto'
18
22
  import { compactWhitespace, truncateText } from '../utils/string'
19
23
  import type { SurrealMemoryStore } from './memory-store'
20
- import { getDefaultMemoryStore } from './memory-store'
24
+ import { createMemoryStore } from './memory-store'
21
25
  import { hashContent, isUniqueIndexConflict } from './memory-store.helpers'
22
26
  import { FactRetrievalSchema, MemoryDeltaSchema, MemoryUpdateSchema } from './memory-types'
23
27
  import type {
@@ -35,6 +39,7 @@ import type {
35
39
  SearchOptions,
36
40
  WeightedSearchOptions,
37
41
  } from './memory-types'
42
+ import type { SurrealDBService } from './service'
38
43
 
39
44
  const MEMORY_WORKER_MODEL_TIMEOUT_MS = 10 * 60 * 1000
40
45
  const MEMORY_FACT_EXTRACTION_TIMEOUT_MS = MEMORY_WORKER_MODEL_TIMEOUT_MS
@@ -44,7 +49,6 @@ const MEMORY_DELTA_MAX_CANDIDATES_PER_FACT = 10
44
49
  const MEMORY_DELTA_MIN_BASELINE_CANDIDATES = 12
45
50
  const MEMORY_DELTA_MEMORY_TEXT_MAX_CHARS = 400
46
51
  const CANDIDATE_FANOUT_MULTIPLIER = 4
47
- const helperModelRuntime = createHelperModelRuntime()
48
52
 
49
53
  interface PreparedScopeUpdate {
50
54
  options: AddOptions
@@ -53,20 +57,34 @@ interface PreparedScopeUpdate {
53
57
  existingMemories: Array<{ id: string; text: string }>
54
58
  }
55
59
 
56
- interface ScopedExistingMemories {
57
- options: AddOptions
58
- existingMemories: Array<{ id: string; text: string }>
59
- scopeMemoryIdsByUnionId: Record<string, string[]>
60
- }
61
-
62
60
  export class Memory {
63
61
  private store: SurrealMemoryStore
64
62
  private createAgent: CreateHelperAgentFn
65
63
  private maxOutputTokens?: number
66
64
  private config: MemoryConfig
67
-
68
- constructor(agent: { createAgent: CreateHelperAgentFn; maxOutputTokens?: number }, config: MemoryConfig = {}) {
69
- this.store = getDefaultMemoryStore()
65
+ private runtimeConfig: ResolvedLotaRuntimeConfig
66
+ private helperModelRuntime: HelperModelRuntime
67
+
68
+ constructor(
69
+ deps: {
70
+ db: SurrealDBService
71
+ runtimeConfig: ResolvedLotaRuntimeConfig
72
+ helperModelRuntime: HelperModelRuntime
73
+ background: Context.Service.Shape<typeof BackgroundWorkService>
74
+ },
75
+ agent: { createAgent: CreateHelperAgentFn; maxOutputTokens?: number },
76
+ config: MemoryConfig = {},
77
+ ) {
78
+ this.store = createMemoryStore(
79
+ deps.db,
80
+ {
81
+ embeddingModel: deps.runtimeConfig.aiGateway.embeddingModel,
82
+ openRouterApiKey: deps.runtimeConfig.aiGateway.openRouterApiKey,
83
+ },
84
+ deps.background,
85
+ )
86
+ this.runtimeConfig = deps.runtimeConfig
87
+ this.helperModelRuntime = deps.helperModelRuntime
70
88
  this.createAgent = agent.createAgent
71
89
  this.maxOutputTokens = agent.maxOutputTokens
72
90
 
@@ -84,10 +102,10 @@ export class Memory {
84
102
  private buildMemoryUnionId(text: string): string | null {
85
103
  const normalized = this.normalizeMemoryDeltaText(text, MEMORY_DELTA_MEMORY_TEXT_MAX_CHARS)
86
104
  if (!normalized) return null
87
- return `union_${new Bun.CryptoHasher('sha256').update(normalized).digest('hex')}`
105
+ return `union_${sha256Hex(normalized)}`
88
106
  }
89
107
 
90
- async insert(
108
+ insert(
91
109
  content: string,
92
110
  options: {
93
111
  scopeId: string
@@ -96,242 +114,320 @@ export class Memory {
96
114
  metadata?: Record<string, unknown>
97
115
  durability?: Durability
98
116
  },
99
- ): Promise<string> {
100
- return this.store.insert(
101
- content,
102
- options.scopeId,
103
- options.memoryType,
104
- options.metadata ?? {},
105
- options.importance ?? 1,
106
- options.durability ?? 'standard',
117
+ ): Effect.Effect<string, MemoryServiceError, never> {
118
+ return tryMemoryPromise('Failed to insert memory.', () =>
119
+ this.store.insert(
120
+ content,
121
+ options.scopeId,
122
+ options.memoryType,
123
+ options.metadata ?? {},
124
+ options.importance ?? 1,
125
+ options.durability ?? 'standard',
126
+ ),
107
127
  )
108
128
  }
109
129
 
110
- async search(query: string, options: SearchOptions): Promise<string> {
111
- const limit = options.limit ?? getRuntimeConfig().memory.searchK
112
- const results = await this.store.search(query, options.scopeId, limit, options.memoryType)
113
-
114
- return formatResults(results)
130
+ search(query: string, options: SearchOptions): Effect.Effect<string, MemoryServiceError, never> {
131
+ return Effect.gen(
132
+ function* (this: Memory) {
133
+ const limit = options.limit ?? this.runtimeConfig.memory.searchK
134
+ const results = yield* tryMemoryPromise('Failed to search memory.', () =>
135
+ this.store.search(query, options.scopeId, limit, options.memoryType),
136
+ )
137
+ return formatResults(results)
138
+ }.bind(this),
139
+ )
115
140
  }
116
141
 
117
- async hybridSearch(query: string, options: SearchOptions): Promise<string> {
118
- const limit = options.limit ?? getRuntimeConfig().memory.searchK
119
- const results = await this.store.hybridSearch(query, options.scopeId, limit, options.memoryType)
120
-
121
- return formatResults(results)
142
+ hybridSearch(query: string, options: SearchOptions): Effect.Effect<string, MemoryServiceError, never> {
143
+ return Effect.gen(
144
+ function* (this: Memory) {
145
+ const limit = options.limit ?? this.runtimeConfig.memory.searchK
146
+ const results = yield* tryMemoryPromise('Failed to perform hybrid search.', () =>
147
+ this.store.hybridSearch(query, options.scopeId, limit, options.memoryType),
148
+ )
149
+ return formatResults(results)
150
+ }.bind(this),
151
+ )
122
152
  }
123
153
 
124
- async hybridSearchWeighted(
154
+ hybridSearchWeighted(
125
155
  query: string,
126
156
  options: SearchOptions & { weights?: [number, number]; normalization?: 'minmax' | 'zscore' },
127
- ): Promise<string> {
128
- const limit = options.limit ?? getRuntimeConfig().memory.searchK
129
- const results = await this.store.hybridSearchWeighted(query, {
130
- scopeId: options.scopeId,
131
- limit,
132
- memoryType: options.memoryType,
133
- weights: options.weights,
134
- normalization: options.normalization,
135
- })
136
-
137
- return formatResults(results)
157
+ ): Effect.Effect<string, MemoryServiceError, never> {
158
+ return Effect.gen(
159
+ function* (this: Memory) {
160
+ const limit = options.limit ?? this.runtimeConfig.memory.searchK
161
+ const results = yield* tryMemoryPromise('Failed to perform weighted hybrid search.', () =>
162
+ this.store.hybridSearchWeighted(query, {
163
+ scopeId: options.scopeId,
164
+ limit,
165
+ memoryType: options.memoryType,
166
+ weights: options.weights,
167
+ normalization: options.normalization,
168
+ }),
169
+ )
170
+ return formatResults(results)
171
+ }.bind(this),
172
+ )
138
173
  }
139
174
 
140
- async searchCandidates(query: string, options: WeightedSearchOptions): Promise<MemorySearchResult[]> {
141
- const limit = options.limit ?? getRuntimeConfig().memory.searchK
142
- const results = await this.store.hybridSearchWeighted(query, {
143
- scopeId: options.scopeId,
144
- limit,
145
- memoryType: options.memoryType,
146
- weights: options.weights,
147
- normalization: options.normalization,
148
- fastMode: options.fastMode,
149
- })
150
-
151
- if (options.fastMode || options.includeNeighborContext === false) {
152
- return results
153
- }
175
+ searchCandidates(
176
+ query: string,
177
+ options: WeightedSearchOptions,
178
+ ): Effect.Effect<MemorySearchResult[], MemoryServiceError, never> {
179
+ return Effect.gen(
180
+ function* (this: Memory) {
181
+ const limit = options.limit ?? this.runtimeConfig.memory.searchK
182
+ const results = yield* tryMemoryPromise('Failed to search memory candidates.', () =>
183
+ this.store.hybridSearchWeighted(query, {
184
+ scopeId: options.scopeId,
185
+ limit,
186
+ memoryType: options.memoryType,
187
+ weights: options.weights,
188
+ normalization: options.normalization,
189
+ fastMode: options.fastMode,
190
+ }),
191
+ )
192
+
193
+ if (options.fastMode || options.includeNeighborContext === false) {
194
+ return results
195
+ }
154
196
 
155
- return this.store.enrichWithNeighbors(results)
197
+ return yield* tryMemoryPromise('Failed to enrich memory candidates.', () =>
198
+ this.store.enrichWithNeighbors(results),
199
+ )
200
+ }.bind(this),
201
+ )
156
202
  }
157
203
 
158
- async listTopMemories(options: {
204
+ listTopMemories(options: {
159
205
  scopeId: string
160
206
  limit: number
161
207
  memoryType?: MemoryType
162
208
  durability?: Durability
163
209
  minImportance?: number
164
- }): Promise<MemoryRecord[]> {
165
- return this.store.listTopMemories(options)
210
+ }): Effect.Effect<MemoryRecord[], MemoryServiceError, never> {
211
+ return tryMemoryPromise('Failed to list top memories.', () => this.store.listTopMemories(options))
166
212
  }
167
213
 
168
- async list(options: MemoryListOptions): Promise<MemoryRecord[]> {
169
- return this.store.list(options)
214
+ list(options: MemoryListOptions): Effect.Effect<MemoryRecord[], MemoryServiceError, never> {
215
+ return tryMemoryPromise('Failed to list memories.', () => this.store.list(options))
170
216
  }
171
217
 
172
- async updateMemory(id: string, newContent: string): Promise<void> {
173
- await this.store.update(id, newContent)
218
+ updateMemory(id: string, newContent: string): Effect.Effect<void, MemoryServiceError, never> {
219
+ return tryMemoryPromise(`Failed to update memory ${id}.`, () => this.store.update(id, newContent))
174
220
  }
175
221
 
176
- async addRelation(fromId: string, toId: string, relationType: RelationType, confidence = 1): Promise<void> {
177
- await this.store.addRelation(fromId, toId, relationType, confidence)
222
+ addRelation(
223
+ fromId: string,
224
+ toId: string,
225
+ relationType: RelationType,
226
+ confidence = 1,
227
+ ): Effect.Effect<void, MemoryServiceError, never> {
228
+ return tryMemoryPromise(`Failed to add ${relationType} relation.`, () =>
229
+ this.store.addRelation(fromId, toId, relationType, confidence),
230
+ )
178
231
  }
179
232
 
180
- async getStaleMemories(scopeId: string, limit?: number): Promise<MemorySearchResult[]> {
181
- return this.store.getStaleMemories(scopeId, limit)
233
+ getStaleMemories(scopeId: string, limit?: number): Effect.Effect<MemorySearchResult[], MemoryServiceError, never> {
234
+ return tryMemoryPromise('Failed to load stale memories.', () => this.store.getStaleMemories(scopeId, limit))
182
235
  }
183
236
 
184
- async extractFactsFromMessages(
237
+ extractFactsFromMessages(
185
238
  messages: Message[],
186
239
  extractionOptions?: { customPrompt?: string; maxFacts?: number },
187
- ): Promise<ExtractedFact[]> {
188
- if (messages.length === 0) return []
189
-
190
- const parsedMessages = parseMessages(messages)
191
-
192
- const facts = await this.extractFacts(parsedMessages, extractionOptions)
193
- if (facts.length === 0) {
194
- aiLogger.debug`No facts extracted from conversation`
195
- return []
196
- }
240
+ ): Effect.Effect<ExtractedFact[], MemoryServiceError, never> {
241
+ return Effect.gen(
242
+ function* (this: Memory) {
243
+ if (messages.length === 0) return []
244
+
245
+ const parsedMessages = parseMessages(messages)
246
+ const facts = yield* this.extractFactsEffect(parsedMessages, extractionOptions)
247
+ if (facts.length === 0) {
248
+ aiLogger.debug`No facts extracted from conversation`
249
+ return []
250
+ }
197
251
 
198
- return facts
252
+ return facts
253
+ }.bind(this),
254
+ )
199
255
  }
200
256
 
201
- async applyFactsToScopes(facts: ExtractedFact[], scopes: AddOptions[]): Promise<void> {
202
- if (facts.length === 0 || scopes.length === 0) return
257
+ applyFactsToScopes(facts: ExtractedFact[], scopes: AddOptions[]): Effect.Effect<void, MemoryServiceError, never> {
258
+ return Effect.gen(
259
+ function* (this: Memory) {
260
+ if (facts.length === 0 || scopes.length === 0) return
261
+ const prepared = yield* this.prepareFactsToScopesEffect(facts, scopes)
262
+ yield* this.applyPreparedScopeUpdates(prepared)
263
+ }.bind(this),
264
+ )
265
+ }
203
266
 
204
- const prepared = await this.prepareFactsToScopes(facts, scopes)
205
- await this.applyPreparedScopeUpdates(prepared)
267
+ prepareFactsToScopes(
268
+ facts: ExtractedFact[],
269
+ scopes: AddOptions[],
270
+ ): Effect.Effect<PreparedScopeUpdate[], MemoryServiceError, never> {
271
+ return this.prepareFactsToScopesEffect(facts, scopes)
206
272
  }
207
273
 
208
- async prepareFactsToScopes(facts: ExtractedFact[], scopes: AddOptions[]): Promise<PreparedScopeUpdate[]> {
209
- if (facts.length === 0 || scopes.length === 0) return []
274
+ applyPreparedScopeUpdates(prepared: PreparedScopeUpdate[]): Effect.Effect<void, MemoryServiceError, never> {
275
+ if (prepared.length === 0) return Effect.void
210
276
 
211
- const factMaps = buildMemoryFactMaps(facts)
212
- const factContents = facts.map((fact) => fact.content)
213
- const scopePayloads: ScopedExistingMemories[] = await Promise.all(
214
- scopes.map(async (scopeOptions) => {
215
- const existingMemories = await this.store.list({
216
- scopeId: scopeOptions.scopeId,
217
- memoryType: scopeOptions.memoryType,
218
- })
219
- const normalizedMemories = existingMemories.map((memory) => ({ id: memory.id, text: memory.content }))
220
- const scopeMemoryIdsByUnionId: Record<string, string[]> = {}
221
- for (const memory of normalizedMemories) {
222
- const unionId = this.buildMemoryUnionId(memory.text)
223
- if (!unionId) continue
224
- ;(scopeMemoryIdsByUnionId[unionId] ??= []).push(memory.id)
225
- }
226
- return { options: scopeOptions, existingMemories: normalizedMemories, scopeMemoryIdsByUnionId }
227
- }),
277
+ return Effect.forEach(
278
+ prepared,
279
+ (item) =>
280
+ tryMemoryPromise('Failed to apply memory updates', () =>
281
+ this.applyUpdates(item.updates, item.options, item.factMaps, item.existingMemories),
282
+ ),
283
+ { concurrency: 2, discard: true },
228
284
  )
229
- const unionMemories = new Map<string, { id: string; text: string }>()
230
- for (const scopePayload of scopePayloads) {
231
- for (const memory of scopePayload.existingMemories) {
232
- const unionId = this.buildMemoryUnionId(memory.text)
233
- if (!unionId || unionMemories.has(unionId)) continue
234
- const normalizedText = this.normalizeMemoryDeltaText(memory.text, MEMORY_DELTA_MEMORY_TEXT_MAX_CHARS)
235
- if (!normalizedText) continue
236
- unionMemories.set(unionId, { id: unionId, text: normalizedText })
237
- }
238
- }
239
-
240
- const delta = await this.determineDelta([...unionMemories.values()], factContents)
241
- return scopePayloads.map(({ options, existingMemories, scopeMemoryIdsByUnionId }) => ({
242
- options,
243
- updates: MemoryUpdateSchema.parse(
244
- compileMemoryUpdatesFromDelta({
245
- existingMemories,
246
- newFacts: factContents,
247
- delta: projectMemoryDeltaToScope({
248
- delta,
249
- scopeMemoryIds: existingMemories.map((memory) => memory.id),
250
- scopeMemoryIdsByUnionId,
251
- }),
252
- }),
253
- ),
254
- factMaps,
255
- existingMemories,
256
- }))
257
285
  }
258
286
 
259
- async applyPreparedScopeUpdates(prepared: PreparedScopeUpdate[]): Promise<void> {
260
- if (prepared.length === 0) return
287
+ private prepareFactsToScopesEffect(
288
+ facts: ExtractedFact[],
289
+ scopes: AddOptions[],
290
+ ): Effect.Effect<PreparedScopeUpdate[], MemoryServiceError, never> {
291
+ return Effect.gen(
292
+ function* (this: Memory) {
293
+ if (facts.length === 0 || scopes.length === 0) return []
294
+
295
+ const factMaps = buildMemoryFactMaps(facts)
296
+ const factContents = facts.map((fact) => fact.content)
297
+ const scopePayloads = yield* Effect.all(
298
+ scopes.map((scopeOptions) =>
299
+ tryMemoryPromise(`Failed to list memories for scope ${scopeOptions.scopeId}.`, () =>
300
+ this.store.list({ scopeId: scopeOptions.scopeId, memoryType: scopeOptions.memoryType }),
301
+ ).pipe(
302
+ Effect.map((existingMemories) => {
303
+ const normalizedMemories = existingMemories.map((memory) => ({ id: memory.id, text: memory.content }))
304
+ const scopeMemoryIdsByUnionId: Record<string, string[]> = {}
305
+ for (const memory of normalizedMemories) {
306
+ const unionId = this.buildMemoryUnionId(memory.text)
307
+ if (!unionId) continue
308
+ ;(scopeMemoryIdsByUnionId[unionId] ??= []).push(memory.id)
309
+ }
310
+ return { options: scopeOptions, existingMemories: normalizedMemories, scopeMemoryIdsByUnionId }
311
+ }),
312
+ ),
313
+ ),
314
+ )
315
+
316
+ const unionMemories = new Map<string, { id: string; text: string }>()
317
+ for (const scopePayload of scopePayloads) {
318
+ for (const memory of scopePayload.existingMemories) {
319
+ const unionId = this.buildMemoryUnionId(memory.text)
320
+ if (!unionId || unionMemories.has(unionId)) continue
321
+ const normalizedText = this.normalizeMemoryDeltaText(memory.text, MEMORY_DELTA_MEMORY_TEXT_MAX_CHARS)
322
+ if (!normalizedText) continue
323
+ unionMemories.set(unionId, { id: unionId, text: normalizedText })
324
+ }
325
+ }
261
326
 
262
- for (const item of prepared) {
263
- await this.applyUpdates(item.updates, item.options, item.factMaps, item.existingMemories)
264
- }
327
+ const delta = yield* this.determineDeltaEffect([...unionMemories.values()], factContents)
328
+ return scopePayloads.map(({ options, existingMemories, scopeMemoryIdsByUnionId }) => ({
329
+ options,
330
+ updates: MemoryUpdateSchema.parse(
331
+ compileMemoryUpdatesFromDelta({
332
+ existingMemories,
333
+ newFacts: factContents,
334
+ delta: projectMemoryDeltaToScope({
335
+ delta,
336
+ scopeMemoryIds: existingMemories.map((memory) => memory.id),
337
+ scopeMemoryIdsByUnionId,
338
+ }),
339
+ }),
340
+ ),
341
+ factMaps,
342
+ existingMemories,
343
+ }))
344
+ }.bind(this),
345
+ )
265
346
  }
266
347
 
267
- private async extractFacts(
348
+ private extractFactsEffect(
268
349
  parsedMessages: string,
269
350
  extractionOptions?: { customPrompt?: string; maxFacts?: number },
270
- ): Promise<ExtractedFact[]> {
271
- const [systemPrompt, userPrompt] = getFactRetrievalMessages(
272
- parsedMessages,
273
- this.buildFactExtractionPrompt(extractionOptions?.customPrompt),
274
- extractionOptions?.maxFacts,
275
- )
276
-
277
- try {
278
- const result = await helperModelRuntime.generateHelperStructured({
279
- tag: 'memory-extract-facts',
280
- createAgent: this.createAgent,
281
- defaultSystemPrompt: this.config.customPrompt,
282
- systemPrompt,
283
- maxOutputTokens: this.maxOutputTokens,
284
- timeoutMs: MEMORY_FACT_EXTRACTION_TIMEOUT_MS,
285
- messages: [{ role: 'user', content: userPrompt }],
286
- schema: FactRetrievalSchema,
287
- })
351
+ ): Effect.Effect<ExtractedFact[], MemoryServiceError, never> {
352
+ return Effect.gen(
353
+ function* (this: Memory) {
354
+ const [systemPrompt, userPrompt] = getFactRetrievalMessages(
355
+ parsedMessages,
356
+ this.buildFactExtractionPrompt(extractionOptions?.customPrompt),
357
+ extractionOptions?.maxFacts,
358
+ )
359
+
360
+ const result = yield* Effect.tryPromise({
361
+ try: () =>
362
+ this.helperModelRuntime.generateHelperStructured({
363
+ tag: 'memory-extract-facts',
364
+ createAgent: this.createAgent,
365
+ defaultSystemPrompt: this.config.customPrompt,
366
+ systemPrompt,
367
+ maxOutputTokens: this.maxOutputTokens,
368
+ timeoutMs: MEMORY_FACT_EXTRACTION_TIMEOUT_MS,
369
+ messages: [{ role: 'user', content: userPrompt }],
370
+ schema: FactRetrievalSchema,
371
+ }),
372
+ catch: (cause) => new MemoryServiceError({ message: 'Failed to extract facts.', cause }),
373
+ })
288
374
 
289
- return postProcessMemoryFacts(
290
- result.facts,
291
- typeof extractionOptions?.maxFacts === 'number' ? { maxFacts: extractionOptions.maxFacts } : {},
292
- )
293
- } catch (error) {
294
- aiLogger.warn`Failed to extract facts: ${error}`
295
- return []
296
- }
375
+ return postProcessMemoryFacts(
376
+ result.facts,
377
+ typeof extractionOptions?.maxFacts === 'number' ? { maxFacts: extractionOptions.maxFacts } : {},
378
+ )
379
+ }.bind(this),
380
+ ).pipe(
381
+ Effect.catchTag('MemoryServiceError', (error) =>
382
+ Effect.sync(() => {
383
+ aiLogger.warn`Failed to extract facts: ${error.message}`
384
+ return []
385
+ }),
386
+ ),
387
+ )
297
388
  }
298
389
 
299
- private async determineDelta(
390
+ private determineDeltaEffect(
300
391
  existingMemories: { id: string; text: string }[],
301
392
  newFacts: string[],
302
- ): Promise<{ deltas: Array<z.infer<typeof MemoryDeltaSchema>['deltas'][number]> }> {
303
- if (existingMemories.length === 0) {
304
- return {
305
- deltas: newFacts.map((fact) => ({
306
- fact,
307
- classification: 'new' as const,
308
- targetMemoryIds: [],
309
- invalidateTargetIds: [],
310
- relations: [],
311
- rationale: 'No existing memories in scope.',
312
- })),
313
- }
314
- }
393
+ ): Effect.Effect<{ deltas: Array<z.infer<typeof MemoryDeltaSchema>['deltas'][number]> }, MemoryServiceError, never> {
394
+ return Effect.gen(
395
+ function* (this: Memory) {
396
+ if (existingMemories.length === 0) {
397
+ return {
398
+ deltas: newFacts.map((fact) => ({
399
+ fact,
400
+ classification: 'new' as const,
401
+ targetMemoryIds: [],
402
+ invalidateTargetIds: [],
403
+ relations: [],
404
+ rationale: 'No existing memories in scope.',
405
+ })),
406
+ }
407
+ }
315
408
 
316
- const candidateMemories = this.selectDeltaCandidateMemories(existingMemories, newFacts)
317
- const { systemPrompt, userPrompt } = getClassifyMemoryDeltaPrompt({ existingMemories: candidateMemories, newFacts })
318
- aiLogger.debug`Memory delta candidate selection (existing=${existingMemories.length}, selected=${candidateMemories.length}, facts=${newFacts.length})`
319
-
320
- try {
321
- const deltas = await helperModelRuntime.generateHelperStructured({
322
- tag: 'memory-classify-delta',
323
- createAgent: this.createAgent,
324
- systemPrompt,
325
- maxOutputTokens: this.maxOutputTokens,
326
- timeoutMs: MEMORY_DELTA_CLASSIFICATION_TIMEOUT_MS,
327
- messages: [{ role: 'user', content: userPrompt }],
328
- schema: MemoryDeltaSchema,
329
- })
330
- return deltas
331
- } catch (error) {
332
- aiLogger.error`Failed to determine memory updates: ${error}`
333
- throw error
334
- }
409
+ const candidateMemories = this.selectDeltaCandidateMemories(existingMemories, newFacts)
410
+ const { systemPrompt, userPrompt } = getClassifyMemoryDeltaPrompt({
411
+ existingMemories: candidateMemories,
412
+ newFacts,
413
+ })
414
+ aiLogger.debug`Memory delta candidate selection (existing=${existingMemories.length}, selected=${candidateMemories.length}, facts=${newFacts.length})`
415
+
416
+ return yield* Effect.tryPromise({
417
+ try: () =>
418
+ this.helperModelRuntime.generateHelperStructured({
419
+ tag: 'memory-classify-delta',
420
+ createAgent: this.createAgent,
421
+ systemPrompt,
422
+ maxOutputTokens: this.maxOutputTokens,
423
+ timeoutMs: MEMORY_DELTA_CLASSIFICATION_TIMEOUT_MS,
424
+ messages: [{ role: 'user', content: userPrompt }],
425
+ schema: MemoryDeltaSchema,
426
+ }),
427
+ catch: (cause) => new MemoryServiceError({ message: 'Failed to determine memory updates.', cause }),
428
+ })
429
+ }.bind(this),
430
+ )
335
431
  }
336
432
 
337
433
  private normalizeMemoryDeltaText(value: string, maxChars?: number): string {
@@ -441,92 +537,112 @@ export class Memory {
441
537
  return selected.map((memory) => ({ id: memory.id, text: memory.text }))
442
538
  }
443
539
 
444
- private async applyUpdates(
540
+ private applyUpdates(
445
541
  updates: MemoryUpdateOutput,
446
542
  options: AddOptions,
447
543
  factMaps: ReturnType<typeof buildMemoryFactMaps>,
448
544
  existingMemories: Array<{ id: string; text: string }>,
449
- ): Promise<void> {
450
- const plan = createMemoryActionPlan({
451
- updates,
452
- memoryType: options.memoryType,
453
- explicitImportance: options.importance,
454
- extractedImportanceByKey: factMaps.extractedImportanceByKey,
455
- confidenceByKey: factMaps.confidenceByKey,
456
- durabilityByKey: factMaps.durabilityByKey,
457
- categoryByKey: factMaps.categoryByKey,
458
- existingMemories,
459
- })
460
-
461
- const idMap = new Map<string, string>()
462
-
463
- for (const action of plan.actions) {
464
- switch (action.type) {
465
- case 'add': {
466
- const truncatedContent = truncateText(action.text, 50)
467
- const metadata = { ...options.metadata, memoryCategory: action.category }
468
- const hash = hashContent(action.text, options.scopeId, options.memoryType)
469
- let newId: string
470
-
471
- try {
472
- newId = await this.store.insert(
473
- action.text,
474
- options.scopeId,
475
- options.memoryType,
476
- metadata,
477
- action.importance,
478
- action.durability as Durability,
479
- )
480
- } catch (error) {
481
- if (!isUniqueIndexConflict(error, 'memoryHashIdx')) {
482
- throw error
545
+ ): Effect.Effect<void, MemoryServiceError, never> {
546
+ return Effect.gen(
547
+ function* (this: Memory) {
548
+ const plan = createMemoryActionPlan({
549
+ updates,
550
+ memoryType: options.memoryType,
551
+ explicitImportance: options.importance,
552
+ extractedImportanceByKey: factMaps.extractedImportanceByKey,
553
+ confidenceByKey: factMaps.confidenceByKey,
554
+ durabilityByKey: factMaps.durabilityByKey,
555
+ categoryByKey: factMaps.categoryByKey,
556
+ existingMemories,
557
+ })
558
+
559
+ const idMap = new Map<string, string>()
560
+ for (const action of plan.actions) {
561
+ switch (action.type) {
562
+ case 'add': {
563
+ const truncatedContent = truncateText(action.text, 50)
564
+ const metadata = { ...options.metadata, memoryCategory: action.category }
565
+ const hash = hashContent(action.text, options.scopeId, options.memoryType)
566
+
567
+ const newId = yield* tryMemoryPromise(`Failed to insert memory for scope ${options.scopeId}.`, () =>
568
+ this.store.insert(
569
+ action.text,
570
+ options.scopeId,
571
+ options.memoryType,
572
+ metadata,
573
+ action.importance,
574
+ action.durability as Durability,
575
+ ),
576
+ ).pipe(
577
+ Effect.catchTag('MemoryServiceError', (error) => {
578
+ if (!isUniqueIndexConflict(error.cause, 'memoryHashIdx')) {
579
+ return Effect.fail(error)
580
+ }
581
+
582
+ return tryMemoryPromise(`Failed to look up memory hash ${hash}.`, () =>
583
+ this.store.getByHash(hash),
584
+ ).pipe(
585
+ Effect.flatMap((existing) => {
586
+ if (!existing) {
587
+ return Effect.fail(error)
588
+ }
589
+
590
+ return Effect.sync(() => {
591
+ aiLogger.debug`Skipped duplicate memory insert due to hash conflict: ${existing.id}`
592
+ return existing.id
593
+ })
594
+ }),
595
+ )
596
+ }),
597
+ )
598
+
599
+ idMap.set(action.refId, newId)
600
+ aiLogger.debug`Added new memory (memoryType: ${options.memoryType}, category: ${action.category}, durability: ${action.durability}, importance: ${action.importance.toFixed(2)}, content: ${truncatedContent})`
601
+ break
483
602
  }
484
603
 
485
- const existing = await this.store.getByHash(hash)
486
- if (!existing) {
487
- throw error
604
+ case 'update': {
605
+ const metadata = { ...options.metadata, ...(action.category ? { memoryCategory: action.category } : {}) }
606
+ yield* tryMemoryPromise(`Failed to update memory ${action.refId}.`, () =>
607
+ this.store.update(action.refId, action.text, {
608
+ importance: action.importance,
609
+ durability: action.durability as Durability | undefined,
610
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
611
+ }),
612
+ )
613
+ idMap.set(action.refId, action.refId)
614
+ aiLogger.debug`Updated memory ${action.refId}: ${truncateText(action.text, 50)}`
615
+ break
488
616
  }
489
617
 
490
- newId = existing.id
491
- aiLogger.debug`Skipped duplicate memory insert due to hash conflict: ${newId}`
618
+ case 'delete':
619
+ yield* tryMemoryPromise(`Failed to delete memory ${action.refId}.`, () => this.store.delete(action.refId))
620
+ aiLogger.debug`Deleted memory ${action.refId}`
621
+ break
492
622
  }
493
-
494
- idMap.set(action.refId, newId)
495
- aiLogger.debug`Added new memory (memoryType: ${options.memoryType}, category: ${action.category}, durability: ${action.durability}, importance: ${action.importance.toFixed(2)}, content: ${truncatedContent})`
496
- break
497
623
  }
498
624
 
499
- case 'update': {
500
- const metadata = { ...options.metadata, ...(action.category ? { memoryCategory: action.category } : {}) }
501
- await this.store.update(action.refId, action.text, {
502
- importance: action.importance,
503
- durability: action.durability as Durability | undefined,
504
- metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
505
- })
506
- idMap.set(action.refId, action.refId)
507
- aiLogger.debug`Updated memory ${action.refId}: ${truncateText(action.text, 50)}`
508
- break
625
+ for (const relation of plan.relations) {
626
+ const fromId = idMap.get(relation.fromRefId)
627
+ if (!fromId) continue
628
+
629
+ const toId = idMap.get(relation.toRefId) ?? relation.toRefId
630
+ yield* tryMemoryPromise(`Failed to create memory relation ${relation.relation}.`, () =>
631
+ this.store.addRelation(fromId, toId, relation.relation),
632
+ ).pipe(
633
+ Effect.tap(() =>
634
+ Effect.sync(() => {
635
+ aiLogger.debug`Created ${relation.relation} relation: ${fromId} -> ${toId}`
636
+ }),
637
+ ),
638
+ Effect.catchTag('MemoryServiceError', (error) =>
639
+ Effect.sync(() => {
640
+ aiLogger.warn`Failed to create memory relation (non-fatal, graph may be incomplete): ${error.message}`
641
+ }),
642
+ ),
643
+ )
509
644
  }
510
-
511
- case 'delete':
512
- await this.store.delete(action.refId)
513
- aiLogger.debug`Deleted memory ${action.refId}`
514
- break
515
- }
516
- }
517
-
518
- for (const relation of plan.relations) {
519
- const fromId = idMap.get(relation.fromRefId)
520
- if (!fromId) continue
521
-
522
- const toId = idMap.get(relation.toRefId) ?? relation.toRefId
523
-
524
- try {
525
- await this.store.addRelation(fromId, toId, relation.relation)
526
- aiLogger.debug`Created ${relation.relation} relation: ${fromId} -> ${toId}`
527
- } catch (error) {
528
- aiLogger.warn`Failed to create memory relation (non-fatal, graph may be incomplete): ${error}`
529
- }
530
- }
645
+ }.bind(this),
646
+ )
531
647
  }
532
648
  }