@lota-sdk/core 0.4.9 → 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 (158) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +38 -10
  4. package/src/config/agent-defaults.ts +22 -9
  5. package/src/config/agent-types.ts +1 -1
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/index.ts +0 -1
  8. package/src/config/logger.ts +20 -7
  9. package/src/config/thread-defaults.ts +12 -4
  10. package/src/create-runtime.ts +69 -656
  11. package/src/db/memory-query-builder.ts +2 -1
  12. package/src/db/memory-store.ts +29 -20
  13. package/src/db/memory.ts +188 -195
  14. package/src/db/service-normalization.ts +97 -64
  15. package/src/db/service.ts +706 -538
  16. package/src/db/startup.ts +30 -19
  17. package/src/effect/awaitable-effect.ts +46 -37
  18. package/src/effect/helpers.ts +30 -5
  19. package/src/effect/index.ts +7 -5
  20. package/src/effect/layers.ts +82 -72
  21. package/src/effect/runtime.ts +18 -3
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -66
  24. package/src/index.ts +13 -11
  25. package/src/queues/autonomous-job.queue.ts +59 -71
  26. package/src/queues/context-compaction.queue.ts +6 -18
  27. package/src/queues/delayed-node-promotion.queue.ts +9 -17
  28. package/src/queues/organization-learning.queue.ts +17 -4
  29. package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
  30. package/src/queues/plan-scheduler.queue.ts +6 -18
  31. package/src/queues/post-chat-memory.queue.ts +6 -18
  32. package/src/queues/queue-factory.ts +128 -50
  33. package/src/queues/title-generation.queue.ts +6 -17
  34. package/src/redis/connection.ts +181 -164
  35. package/src/redis/runtime-connection.ts +13 -3
  36. package/src/redis/stream-context.ts +17 -9
  37. package/src/runtime/agent-runtime-policy.ts +1 -1
  38. package/src/runtime/agent-stream-helpers.ts +15 -11
  39. package/src/runtime/chat-run-orchestration.ts +1 -1
  40. package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
  41. package/src/runtime/context-compaction/context-compaction.ts +126 -82
  42. package/src/runtime/domain-layer.ts +192 -0
  43. package/src/runtime/graph-designer.ts +15 -7
  44. package/src/runtime/helper-model.ts +8 -4
  45. package/src/runtime/index.ts +0 -1
  46. package/src/runtime/memory/memory-block.ts +19 -9
  47. package/src/runtime/memory/memory-pipeline.ts +53 -66
  48. package/src/runtime/memory/memory-scope.ts +33 -29
  49. package/src/runtime/plugin-resolution.ts +33 -54
  50. package/src/runtime/post-turn-side-effects.ts +6 -26
  51. package/src/runtime/retrieval-adapters.ts +4 -4
  52. package/src/runtime/runtime-accessors.ts +92 -0
  53. package/src/runtime/runtime-config.ts +3 -3
  54. package/src/runtime/runtime-extensions.ts +20 -9
  55. package/src/runtime/runtime-lifecycle.ts +124 -0
  56. package/src/runtime/runtime-services.ts +386 -0
  57. package/src/runtime/runtime-token.ts +47 -0
  58. package/src/runtime/social-chat/social-chat-agent-runner.ts +7 -5
  59. package/src/runtime/social-chat/social-chat-history.ts +21 -12
  60. package/src/runtime/social-chat/social-chat.ts +401 -365
  61. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
  62. package/src/runtime/thread-turn-context.ts +21 -27
  63. package/src/services/agent-activity.service.ts +1 -1
  64. package/src/services/agent-executor.service.ts +179 -187
  65. package/src/services/artifact.service.ts +10 -5
  66. package/src/services/attachment.service.ts +35 -1
  67. package/src/services/autonomous-job.service.ts +58 -56
  68. package/src/services/background-work.service.ts +54 -0
  69. package/src/services/chat-run-registry.service.ts +3 -1
  70. package/src/services/context-compaction.service.ts +1 -1
  71. package/src/services/document-chunk.service.ts +8 -17
  72. package/src/services/execution-plan/execution-plan-graph.ts +74 -52
  73. package/src/services/execution-plan/execution-plan.service.ts +1 -1
  74. package/src/services/feedback-loop.service.ts +1 -1
  75. package/src/services/global-orchestrator.service.ts +33 -10
  76. package/src/services/graph-full-routing.ts +44 -33
  77. package/src/services/index.ts +1 -0
  78. package/src/services/institutional-memory.service.ts +8 -17
  79. package/src/services/learned-skill.service.ts +38 -35
  80. package/src/services/memory/memory-errors.ts +27 -0
  81. package/src/services/memory/memory-org-memory.ts +14 -3
  82. package/src/services/memory/memory-preseeded.ts +10 -4
  83. package/src/services/memory/memory-utils.ts +2 -1
  84. package/src/services/memory/memory.service.ts +26 -44
  85. package/src/services/memory/rerank.service.ts +3 -11
  86. package/src/services/monitoring-window.service.ts +1 -1
  87. package/src/services/mutating-approval.service.ts +1 -1
  88. package/src/services/node-workspace.service.ts +2 -2
  89. package/src/services/notification.service.ts +16 -4
  90. package/src/services/organization-member.service.ts +1 -1
  91. package/src/services/organization.service.ts +34 -51
  92. package/src/services/ownership-dispatcher.service.ts +132 -90
  93. package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
  94. package/src/services/plan/plan-agent-query.service.ts +1 -1
  95. package/src/services/plan/plan-approval.service.ts +52 -48
  96. package/src/services/plan/plan-artifact.service.ts +2 -2
  97. package/src/services/plan/plan-builder.service.ts +2 -2
  98. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  99. package/src/services/plan/plan-compiler.service.ts +1 -1
  100. package/src/services/plan/plan-completion-side-effects.ts +18 -24
  101. package/src/services/plan/plan-coordination.service.ts +1 -1
  102. package/src/services/plan/plan-cycle.service.ts +171 -164
  103. package/src/services/plan/plan-deadline.service.ts +290 -304
  104. package/src/services/plan/plan-event-delivery.service.ts +44 -39
  105. package/src/services/plan/plan-executor-graph.ts +114 -67
  106. package/src/services/plan/plan-executor-helpers.ts +60 -75
  107. package/src/services/plan/plan-executor.service.ts +550 -467
  108. package/src/services/plan/plan-run.service.ts +12 -19
  109. package/src/services/plan/plan-scheduler.service.ts +27 -33
  110. package/src/services/plan/plan-template.service.ts +1 -1
  111. package/src/services/plan/plan-transaction-events.ts +8 -5
  112. package/src/services/plan/plan-validator.service.ts +1 -1
  113. package/src/services/plan/plan-workspace.service.ts +17 -11
  114. package/src/services/plugin-executor.service.ts +26 -21
  115. package/src/services/quality-metrics.service.ts +1 -1
  116. package/src/services/queue-job.service.ts +8 -17
  117. package/src/services/recent-activity-title.service.ts +17 -9
  118. package/src/services/recent-activity.service.ts +1 -1
  119. package/src/services/skill-resolver.service.ts +1 -1
  120. package/src/services/social-chat-history.service.ts +37 -20
  121. package/src/services/system-executor.service.ts +25 -20
  122. package/src/services/thread/thread-bootstrap.ts +26 -10
  123. package/src/services/thread/thread-listing.ts +2 -1
  124. package/src/services/thread/thread-memory-block.ts +18 -5
  125. package/src/services/thread/thread-message.service.ts +24 -8
  126. package/src/services/thread/thread-title.service.ts +1 -1
  127. package/src/services/thread/thread-turn-execution.ts +1 -1
  128. package/src/services/thread/thread-turn-preparation.service.ts +18 -16
  129. package/src/services/thread/thread-turn-streaming.ts +12 -11
  130. package/src/services/thread/thread-turn.ts +43 -10
  131. package/src/services/thread/thread.service.ts +11 -2
  132. package/src/services/user.service.ts +1 -1
  133. package/src/services/write-intent-validator.service.ts +1 -1
  134. package/src/storage/attachment-storage.service.ts +7 -4
  135. package/src/storage/generated-document-storage.service.ts +1 -1
  136. package/src/system-agents/context-compaction.agent.ts +1 -1
  137. package/src/system-agents/helper-agent-options.ts +1 -1
  138. package/src/system-agents/memory-reranker.agent.ts +1 -1
  139. package/src/system-agents/memory.agent.ts +1 -1
  140. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  141. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  142. package/src/system-agents/skill-extractor.agent.ts +1 -1
  143. package/src/system-agents/skill-manager.agent.ts +1 -1
  144. package/src/system-agents/title-generator.agent.ts +1 -1
  145. package/src/tools/execution-plan.tool.ts +28 -17
  146. package/src/tools/fetch-webpage.tool.ts +20 -13
  147. package/src/tools/firecrawl-client.ts +13 -3
  148. package/src/tools/plan-approval.tool.ts +9 -1
  149. package/src/tools/search-web.tool.ts +16 -9
  150. package/src/tools/team-think.tool.ts +2 -2
  151. package/src/utils/async.ts +15 -6
  152. package/src/utils/errors.ts +27 -15
  153. package/src/workers/bootstrap.ts +25 -48
  154. package/src/workers/organization-learning.worker.ts +1 -1
  155. package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
  156. package/src/workers/worker-utils.ts +20 -2
  157. package/src/config/search.ts +0 -3
  158. package/src/runtime/agent-types.ts +0 -1
@@ -3,8 +3,9 @@
3
3
  * Builds type-safe SQL queries with proper parameterization
4
4
  */
5
5
 
6
+ import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '@lota-sdk/shared'
7
+
6
8
  import { validateKnnLimit } from '../config/constants'
7
- import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../config/search'
8
9
  import { TABLES } from './tables'
9
10
 
10
11
  const MEMORY_TABLE = TABLES.MEMORY
@@ -1,9 +1,10 @@
1
+ import type { Context } from 'effect'
1
2
  import { Schema, Duration, Effect, Metric, Schedule } from 'effect'
2
3
  import { BoundQuery, eq, inside } from 'surrealdb'
3
4
 
4
5
  import { aiLogger } from '../config/logger'
5
- import { DEFAULT_MEMORY_SEARCH_LIMIT } from '../config/search'
6
6
  import { ProviderEmbeddings } from '../embeddings/provider'
7
+ import type { BackgroundWorkService } from '../services/background-work.service'
7
8
  import { clampImportance, truncateText } from '../utils/string'
8
9
  import { memoryQueryBuilder } from './memory-query-builder'
9
10
  import type { RelationCounts } from './memory-store.helpers'
@@ -24,9 +25,12 @@ import type { SurrealDBError } from './service-normalization'
24
25
  import { TABLES } from './tables'
25
26
  import { isRetriableTransactionConflict } from './transaction-conflict'
26
27
 
28
+ type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkService>
29
+
27
30
  const MEMORY_TABLE = TABLES.MEMORY
28
31
  const MEMORY_HISTORY_TABLE = TABLES.MEMORY_HISTORY
29
32
  const MEMORY_RELATION_TABLE = TABLES.MEMORY_RELATION
33
+ const DEFAULT_MEMORY_SEARCH_LIMIT = 10
30
34
  const MIN_RELEVANCE_SCORE = 0.25
31
35
  const STRONG_GRAPH_BOOSTS = { support: 0.1, contradict: 0.2 } as const
32
36
  const WEAK_GRAPH_BOOSTS = { support: 0.05, contradict: 0.1 } as const
@@ -46,13 +50,9 @@ class MemoryStoreError extends Schema.TaggedErrorClass<MemoryStoreError>()('Memo
46
50
  cause: Schema.Defect,
47
51
  }) {}
48
52
 
49
- function recordMemorySearchDuration(elapsed: number): void {
50
- void Effect.runFork(Metric.update(memorySearchDuration, elapsed))
51
- }
52
-
53
- function tryMemoryStorePromise<A, R = never>(
53
+ function tryMemoryStorePromise<A, E, R = never>(
54
54
  message: string,
55
- thunk: () => PromiseLike<A> | Effect.Effect<A, unknown, R>,
55
+ thunk: () => PromiseLike<A> | Effect.Effect<A, E, R>,
56
56
  ): Effect.Effect<A, MemoryStoreError, R> {
57
57
  return Effect.suspend(() => {
58
58
  try {
@@ -75,9 +75,15 @@ interface EmbeddingClient {
75
75
  export class SurrealMemoryStore {
76
76
  private db: SurrealDBService
77
77
  private embeddings: EmbeddingClient
78
- constructor(db: SurrealDBService, embeddings: EmbeddingClient) {
78
+ private background: BackgroundWorker
79
+ constructor(db: SurrealDBService, embeddings: EmbeddingClient, background: BackgroundWorker) {
79
80
  this.db = db
80
81
  this.embeddings = embeddings
82
+ this.background = background
83
+ }
84
+
85
+ private recordMemorySearchDuration(elapsed: number): Effect.Effect<void> {
86
+ return this.background.runForget(Metric.update(memorySearchDuration, elapsed), 'memory-store.recordSearchDuration')
81
87
  }
82
88
 
83
89
  private toMetadataFieldPathEffect(key: string): Effect.Effect<string, MemoryStoreError> {
@@ -214,7 +220,7 @@ export class SurrealMemoryStore {
214
220
  MIN_RELEVANCE_SCORE,
215
221
  )
216
222
 
217
- this.touchMemories(processed.map((row) => row.id))
223
+ yield* this.touchMemories(processed.map((row) => row.id))
218
224
  return processed
219
225
  }.bind(this),
220
226
  )
@@ -295,8 +301,8 @@ export class SurrealMemoryStore {
295
301
  )
296
302
  }
297
303
 
298
- private touchMemories(memoryIds: string[]): void {
299
- if (memoryIds.length === 0) return
304
+ private touchMemories(memoryIds: string[]): Effect.Effect<void> {
305
+ if (memoryIds.length === 0) return Effect.void
300
306
  const uniqueIds = [...new Set(memoryIds)]
301
307
  const memoryRefs = uniqueIds.map((id) => ensureRecordId(id, TABLES.MEMORY))
302
308
  const sql = `
@@ -306,7 +312,7 @@ export class SurrealMemoryStore {
306
312
  `
307
313
  const query = new BoundQuery(sql, { memoryIds: memoryRefs })
308
314
 
309
- void Effect.runFork(this.runTouchMemoriesWithRetry(query))
315
+ return this.background.runForget(this.runTouchMemoriesWithRetry(query), 'memory-store.touchMemories')
310
316
  }
311
317
 
312
318
  private runTouchMemoriesWithRetry(query: BoundQuery): Effect.Effect<void, never, never> {
@@ -387,7 +393,7 @@ export class SurrealMemoryStore {
387
393
  MIN_RELEVANCE_SCORE,
388
394
  )
389
395
 
390
- this.touchMemories(processed.map((row) => row.id))
396
+ yield* this.touchMemories(processed.map((row) => row.id))
391
397
  return processed
392
398
  }.bind(this),
393
399
  )
@@ -569,7 +575,7 @@ export class SurrealMemoryStore {
569
575
  )
570
576
 
571
577
  aiLogger.debug`Memory store search final results: ${processed.length} memories after filtering`
572
- this.touchMemories(processed.map((row) => row.id))
578
+ yield* this.touchMemories(processed.map((row) => row.id))
573
579
  return processed
574
580
  }.bind(this),
575
581
  )
@@ -614,7 +620,7 @@ export class SurrealMemoryStore {
614
620
  MIN_RELEVANCE_SCORE,
615
621
  )
616
622
 
617
- this.touchMemories(processed.map((row) => row.id))
623
+ yield* this.touchMemories(processed.map((row) => row.id))
618
624
  return processed
619
625
  }.bind(this),
620
626
  )
@@ -676,12 +682,13 @@ export class SurrealMemoryStore {
676
682
 
677
683
  type LinearRow = BasicSearchRow & { linearScore: number }
678
684
 
685
+ const recordSearchDuration = this.recordMemorySearchDuration.bind(this)
679
686
  const linearResults = yield* this.queryFinalStatementEffect<LinearRow>(sql, bindVars).pipe(
680
687
  Effect.timeout(Duration.millis(SurrealMemoryStore.HYBRID_SEARCH_TIMEOUT_MS)),
681
688
  Effect.catchTag('TimeoutError', () =>
682
- Effect.sync(() => {
689
+ Effect.gen(function* () {
683
690
  const elapsed = performance.now() - searchStart
684
- recordMemorySearchDuration(elapsed)
691
+ yield* recordSearchDuration(elapsed)
685
692
  aiLogger.warn`Hybrid search timed out after ${elapsed.toFixed(0)}ms (scopeId: ${options.scopeId}). Falling back to vector-only.`
686
693
  return null
687
694
  }),
@@ -737,9 +744,9 @@ export class SurrealMemoryStore {
737
744
  }
738
745
 
739
746
  const elapsed = performance.now() - searchStart
740
- recordMemorySearchDuration(elapsed)
741
- aiLogger.info`[SUCCESS_WEIGHTED_SEARCH] Weighted hybrid search succeeded (scopeId: ${options.scopeId}, rawResults: ${linearResults.length}, returned: ${processed.length}, weights: ${weights.join(',')}, normalization: ${normalization}, latencyMs: ${elapsed.toFixed(0)})`
742
- this.touchMemories(processed.map((row) => row.id))
747
+ yield* this.recordMemorySearchDuration(elapsed)
748
+ aiLogger.debug`[SUCCESS_WEIGHTED_SEARCH] Weighted hybrid search succeeded (scopeId: ${options.scopeId}, rawResults: ${linearResults.length}, returned: ${processed.length}, weights: ${weights.join(',')}, normalization: ${normalization}, latencyMs: ${elapsed.toFixed(0)})`
749
+ yield* this.touchMemories(processed.map((row) => row.id))
743
750
  return processed
744
751
  }.bind(this),
745
752
  )
@@ -1247,9 +1254,11 @@ export class SurrealMemoryStore {
1247
1254
  export function createMemoryStore(
1248
1255
  db: SurrealDBService,
1249
1256
  options: { embeddingModel: string; openRouterApiKey?: string },
1257
+ background: BackgroundWorker,
1250
1258
  ): SurrealMemoryStore {
1251
1259
  return new SurrealMemoryStore(
1252
1260
  db,
1253
1261
  new ProviderEmbeddings({ modelId: options.embeddingModel, openRouterApiKey: options.openRouterApiKey }),
1262
+ background,
1254
1263
  )
1255
1264
  }
package/src/db/memory.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Schema, Effect } from 'effect'
1
+ import type { Context } from 'effect'
2
+ import { Effect } from 'effect'
2
3
  import type { z } from 'zod'
3
4
 
4
5
  import { aiLogger } from '../config/logger'
@@ -15,6 +16,8 @@ import { getFactRetrievalMessages } from '../runtime/memory/memory-prompts-fact'
15
16
  import { parseMessages } from '../runtime/memory/memory-prompts-parse'
16
17
  import { getClassifyMemoryDeltaPrompt } from '../runtime/memory/memory-prompts-update'
17
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'
18
21
  import { sha256Hex } from '../utils/crypto'
19
22
  import { compactWhitespace, truncateText } from '../utils/string'
20
23
  import type { SurrealMemoryStore } from './memory-store'
@@ -54,32 +57,6 @@ interface PreparedScopeUpdate {
54
57
  existingMemories: Array<{ id: string; text: string }>
55
58
  }
56
59
 
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
- })
81
- }
82
-
83
60
  export class Memory {
84
61
  private store: SurrealMemoryStore
85
62
  private createAgent: CreateHelperAgentFn
@@ -89,14 +66,23 @@ export class Memory {
89
66
  private helperModelRuntime: HelperModelRuntime
90
67
 
91
68
  constructor(
92
- deps: { db: SurrealDBService; runtimeConfig: ResolvedLotaRuntimeConfig; helperModelRuntime: HelperModelRuntime },
69
+ deps: {
70
+ db: SurrealDBService
71
+ runtimeConfig: ResolvedLotaRuntimeConfig
72
+ helperModelRuntime: HelperModelRuntime
73
+ background: Context.Service.Shape<typeof BackgroundWorkService>
74
+ },
93
75
  agent: { createAgent: CreateHelperAgentFn; maxOutputTokens?: number },
94
76
  config: MemoryConfig = {},
95
77
  ) {
96
- this.store = createMemoryStore(deps.db, {
97
- embeddingModel: deps.runtimeConfig.aiGateway.embeddingModel,
98
- openRouterApiKey: deps.runtimeConfig.aiGateway.openRouterApiKey,
99
- })
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
+ )
100
86
  this.runtimeConfig = deps.runtimeConfig
101
87
  this.helperModelRuntime = deps.helperModelRuntime
102
88
  this.createAgent = agent.createAgent
@@ -142,73 +128,77 @@ export class Memory {
142
128
  }
143
129
 
144
130
  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
- })
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
+ )
153
140
  }
154
141
 
155
142
  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
- })
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
+ )
164
152
  }
165
153
 
166
154
  hybridSearchWeighted(
167
155
  query: string,
168
156
  options: SearchOptions & { weights?: [number, number]; normalization?: 'minmax' | 'zscore' },
169
157
  ): 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)
183
- })
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
+ )
184
173
  }
185
174
 
186
175
  searchCandidates(
187
176
  query: string,
188
177
  options: WeightedSearchOptions,
189
178
  ): 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
- )
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
+ )
203
192
 
204
- if (options.fastMode || options.includeNeighborContext === false) {
205
- return results
206
- }
193
+ if (options.fastMode || options.includeNeighborContext === false) {
194
+ return results
195
+ }
207
196
 
208
- return yield* tryMemoryPromise('Failed to enrich memory candidates.', () =>
209
- self.store.enrichWithNeighbors(results),
210
- )
211
- })
197
+ return yield* tryMemoryPromise('Failed to enrich memory candidates.', () =>
198
+ this.store.enrichWithNeighbors(results),
199
+ )
200
+ }.bind(this),
201
+ )
212
202
  }
213
203
 
214
204
  listTopMemories(options: {
@@ -248,28 +238,30 @@ export class Memory {
248
238
  messages: Message[],
249
239
  extractionOptions?: { customPrompt?: string; maxFacts?: number },
250
240
  ): 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
- }
241
+ return Effect.gen(
242
+ function* (this: Memory) {
243
+ if (messages.length === 0) return []
261
244
 
262
- return facts
263
- })
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
+ }
251
+
252
+ return facts
253
+ }.bind(this),
254
+ )
264
255
  }
265
256
 
266
257
  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
- })
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
+ )
273
265
  }
274
266
 
275
267
  prepareFactsToScopes(
@@ -551,105 +543,106 @@ export class Memory {
551
543
  factMaps: ReturnType<typeof buildMemoryFactMaps>,
552
544
  existingMemories: Array<{ id: string; text: string }>,
553
545
  ): 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
- })
566
-
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
- }),
605
- )
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
+ })
606
558
 
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
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
602
+ }
603
+
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
616
+ }
617
+
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
610
622
  }
623
+ }
611
624
 
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,
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}`
619
636
  }),
620
- )
621
- idMap.set(action.refId, action.refId)
622
- aiLogger.debug`Updated memory ${action.refId}: ${truncateText(action.text, 50)}`
623
- break
624
- }
625
-
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
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
+ )
630
644
  }
631
- }
632
-
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
- )
652
- }
653
- })
645
+ }.bind(this),
646
+ )
654
647
  }
655
648
  }