@lota-sdk/core 0.4.7 → 0.4.9

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