@lota-sdk/core 0.4.8 → 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/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  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
@@ -0,0 +1,692 @@
1
+ import { Context, Schema, Effect, Layer } from 'effect'
2
+
3
+ import { aiLogger } from '../../config/logger'
4
+ import type { Memory } from '../../db/memory'
5
+ import { isUniqueIndexConflict } from '../../db/memory-store.helpers'
6
+ import type {
7
+ AddOptions,
8
+ ExtractedFact,
9
+ MemoryListScalar,
10
+ MemoryRecord,
11
+ MemorySearchResult,
12
+ MemoryType,
13
+ RelationType,
14
+ } from '../../db/memory-types'
15
+ import { DatabaseServiceTag, RuntimeConfigServiceTag } from '../../effect/services'
16
+ import { withOrgMemoryLockEffect } from '../../redis/org-memory-lock'
17
+ import type { HelperModelRuntime } from '../../runtime/helper-model'
18
+ import { HelperModelTag } from '../../runtime/helper-model'
19
+ import { ORG_SCOPE_PREFIX, agentScopeId, scopeId } from '../../runtime/memory/memory-scope'
20
+ import {
21
+ countScopedRetrievalCandidates,
22
+ executeScopedRetrieval,
23
+ scopedRetrievalToMap,
24
+ } from '../../runtime/retrieval-adapters'
25
+ import type { ResolvedLotaRuntimeConfig } from '../../runtime/runtime-config'
26
+ import { clampImportance, compactWhitespace } from '../../utils/string'
27
+ import type { MemoryRerankOutput } from './memory-rerank'
28
+ import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory-utils'
29
+ export type { MemoryRerankOutput } from './memory-rerank'
30
+ import {
31
+ buildConversationMessages,
32
+ isRoutableAgentName,
33
+ resolveAgentScopeNames,
34
+ shouldSkipExtractedFacts,
35
+ } from './memory-conversation'
36
+ import { getOrgMemory, makeOrgMemoryCache } from './memory-org-memory'
37
+ import type { OrgMemoryCache } from './memory-org-memory'
38
+ import { getTopMemoriesSection } from './memory-preseeded'
39
+ import { rerankCandidates, rerankCandidatesMultiScope } from './memory-rerank'
40
+ import type { makeRerankService } from './rerank.service'
41
+ import { RerankServiceTag } from './rerank.service'
42
+
43
+ const ORG_MEMORY_TYPE = 'fact'
44
+ const MAX_MEMORY_RESULTS_PER_SCOPE = 10
45
+ const ONBOARDING_MEMORY_MAX_FACTS = 16
46
+ const ONBOARDING_MEMORY_EXTRACTION_PROMPT =
47
+ 'Onboarding mode is active. Extract multiple concrete startup facts from user-provided context: company mission, product capabilities, customer segments, pricing, traction, go-to-market plans, roadmap, team composition, technical stack, risks, and referenced URLs. Prefer one fact per concrete claim.'
48
+ const DIRECT_MEMORY_ASSESSMENT_PROMPT =
49
+ 'The user is submitting a direct memory candidate. Keep the wording faithful. Return one fact only when the statement is durable enough for memory; otherwise return no facts.'
50
+
51
+ class MemoryServiceError extends Schema.TaggedErrorClass<MemoryServiceError>()('MemoryServiceError', {
52
+ message: Schema.String,
53
+ cause: Schema.Defect,
54
+ }) {}
55
+
56
+ class InvalidAgentNameError extends Schema.TaggedErrorClass<InvalidAgentNameError>()('InvalidAgentNameError', {
57
+ agentName: Schema.String,
58
+ }) {}
59
+
60
+ function memoryTryPromise<A>(
61
+ message: string,
62
+ thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
63
+ ): Effect.Effect<A, MemoryServiceError> {
64
+ return Effect.suspend(() => {
65
+ try {
66
+ const value = thunk()
67
+ if (Effect.isEffect(value)) {
68
+ return value.pipe(Effect.mapError((cause) => new MemoryServiceError({ message, cause })))
69
+ }
70
+
71
+ return Effect.tryPromise({
72
+ try: () => Promise.resolve(value),
73
+ catch: (cause) => new MemoryServiceError({ message, cause }),
74
+ })
75
+ } catch (cause) {
76
+ return Effect.fail(new MemoryServiceError({ message, cause }))
77
+ }
78
+ })
79
+ }
80
+
81
+ type EffectSuccess<T> = T extends Effect.Effect<infer A, infer _E, infer _R> ? A : Awaited<T>
82
+
83
+ function searchMemoriesEffect({
84
+ runtimeConfig,
85
+ helperModelRuntime,
86
+ resolveReranker,
87
+ query,
88
+ memory,
89
+ scopeId,
90
+ memoryType,
91
+ fastMode = true,
92
+ }: {
93
+ runtimeConfig: ResolvedLotaRuntimeConfig
94
+ helperModelRuntime: HelperModelRuntime
95
+ resolveReranker: () => ReturnType<typeof makeRerankService>
96
+ query: string
97
+ memory: Memory
98
+ scopeId: string
99
+ memoryType: MemoryType
100
+ fastMode?: boolean
101
+ }): Effect.Effect<string, MemoryServiceError> {
102
+ return Effect.gen(function* () {
103
+ const limit = runtimeConfig.memory.searchK
104
+ const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
105
+ const candidates = yield* memoryTryPromise('Failed to search memory candidates.', () =>
106
+ memory.searchCandidates(query, {
107
+ scopeId,
108
+ limit: candidateLimit,
109
+ memoryType,
110
+ fastMode,
111
+ includeNeighborContext: !fastMode,
112
+ }),
113
+ )
114
+
115
+ aiLogger.debug`Memory search candidates (scopeId: ${scopeId}, candidates: ${candidates.length})`
116
+
117
+ if (candidates.length === 0) {
118
+ return 'No stored memories.'
119
+ }
120
+
121
+ if (fastMode || candidates.length <= limit) {
122
+ aiLogger.debug`Skipping reranking (candidates: ${candidates.length} <= limit: ${limit})`
123
+ return formatMemoryResults(candidates.slice(0, limit))
124
+ }
125
+
126
+ const reranked = yield* rerankCandidates(
127
+ runtimeConfig,
128
+ helperModelRuntime,
129
+ query,
130
+ candidates,
131
+ limit,
132
+ resolveReranker(),
133
+ )
134
+ return formatRerankedResults(reranked, candidates, limit)
135
+ })
136
+ }
137
+
138
+ function applyPreparedScopeUpdatesEffect(params: {
139
+ orgId: string
140
+ orgMemory: Memory
141
+ preparedUpdates: EffectSuccess<ReturnType<Memory['prepareFactsToScopes']>>
142
+ acquireLock?: boolean
143
+ }) {
144
+ return Effect.gen(function* () {
145
+ if (params.preparedUpdates.length === 0) {
146
+ return
147
+ }
148
+
149
+ if (params.acquireLock === false) {
150
+ yield* memoryTryPromise('Failed to apply prepared memory scope updates.', () =>
151
+ params.orgMemory.applyPreparedScopeUpdates(params.preparedUpdates),
152
+ )
153
+ return
154
+ }
155
+
156
+ yield* withOrgMemoryLockEffect(params.orgId, () =>
157
+ memoryTryPromise('Failed to apply prepared memory scope updates.', () =>
158
+ params.orgMemory.applyPreparedScopeUpdates(params.preparedUpdates),
159
+ ),
160
+ )
161
+ })
162
+ }
163
+
164
+ function formatBatchedResults(
165
+ reranked: MemoryRerankOutput | null,
166
+ candidatesByScopeTag: Map<string, MemorySearchResult[]>,
167
+ limit: number,
168
+ agentName?: string,
169
+ ): string {
170
+ if (reranked && reranked.sections.length > 0) {
171
+ const allCandidates = Array.from(candidatesByScopeTag.values()).flat()
172
+ return formatRerankedResults(reranked, allCandidates, limit)
173
+ }
174
+
175
+ const sections: string[] = []
176
+
177
+ if (agentName) {
178
+ const agentCandidates = candidatesByScopeTag.get(`agent:${agentName}`) ?? []
179
+ if (agentCandidates.length > 0) {
180
+ sections.push(`Agent memory (${agentName}):\n${formatMemoryResults(agentCandidates.slice(0, limit))}`)
181
+ } else {
182
+ sections.push(`Agent memory (${agentName}):\nNo stored memories.`)
183
+ }
184
+ }
185
+
186
+ const orgCandidates = candidatesByScopeTag.get('org') ?? []
187
+ if (orgCandidates.length > 0) {
188
+ sections.push(
189
+ `${agentName ? 'Global org memory' : 'Organization memory'}:\n${formatMemoryResults(orgCandidates.slice(0, limit))}`,
190
+ )
191
+ } else {
192
+ sections.push(`${agentName ? 'Global org memory' : 'Organization memory'}:\nNo stored memories.`)
193
+ }
194
+
195
+ return sections.join('\n\n')
196
+ }
197
+
198
+ interface MemoryServiceDeps {
199
+ runtimeConfig: ResolvedLotaRuntimeConfig
200
+ rerankService: ReturnType<typeof makeRerankService>
201
+ helperModelRuntime: HelperModelRuntime
202
+ orgMemoryCache: OrgMemoryCache
203
+ }
204
+
205
+ export function createMemoryService(deps: MemoryServiceDeps) {
206
+ const { runtimeConfig, helperModelRuntime, orgMemoryCache } = deps
207
+ const resolveRerankService = () => deps.rerankService
208
+ const service = {
209
+ searchOrganizationMemories: Effect.fn('MemoryService.searchOrganizationMemories')(function* (
210
+ orgId: string,
211
+ query: string,
212
+ ) {
213
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
214
+ aiLogger.debug`Organization memory search requested (orgId: ${orgId}, scopeId: ${orgScopeId}, queryLength: ${query.length})`
215
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
216
+ const results = yield* searchMemoriesEffect({
217
+ runtimeConfig,
218
+ helperModelRuntime,
219
+ resolveReranker: resolveRerankService,
220
+ query,
221
+ memory,
222
+ scopeId: orgScopeId,
223
+ memoryType: ORG_MEMORY_TYPE,
224
+ })
225
+ aiLogger.debug`Organization memory search completed (resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
226
+ return results
227
+ }),
228
+
229
+ getStaleMemories(orgId: string) {
230
+ return Effect.gen(function* () {
231
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
232
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
233
+ return yield* memoryTryPromise('Failed to load stale memories.', () => memory.getStaleMemories(orgScopeId))
234
+ }).pipe(
235
+ Effect.withSpan('MemoryService.getStaleMemories'),
236
+ Effect.map((stale) => {
237
+ if (stale.length === 0) return ''
238
+ const items = stale.map((m) => `- [NEEDS REVIEW] ${m.content}`).join('\n')
239
+ return `Memories flagged for review (parent fact was superseded):\n${items}`
240
+ }),
241
+ Effect.catch((error) => {
242
+ aiLogger.error`Failed to get stale memories: ${error.cause}`
243
+ return Effect.succeed('')
244
+ }),
245
+ )
246
+ },
247
+
248
+ searchOrganizationMemoriesRaw: Effect.fn('MemoryService.searchOrganizationMemoriesRaw')(function* (
249
+ orgId: string,
250
+ query: string,
251
+ options?: { fastMode?: boolean; limit?: number },
252
+ ) {
253
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
254
+ aiLogger.debug`searchOrganizationMemoriesRaw - orgId: "${orgId}", scopeId: "${orgScopeId}"`
255
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
256
+ const fastMode = options?.fastMode ?? true
257
+ const searchK = runtimeConfig.memory.searchK
258
+ const limit = options?.limit ?? (fastMode ? Math.min(searchK, 4) : searchK)
259
+ const candidates = yield* memoryTryPromise('Failed to search organization memories.', () =>
260
+ memory.searchCandidates(query, {
261
+ scopeId: orgScopeId,
262
+ limit,
263
+ memoryType: ORG_MEMORY_TYPE,
264
+ fastMode,
265
+ includeNeighborContext: !fastMode,
266
+ }),
267
+ )
268
+ aiLogger.debug`Organization memory search (raw) completed (candidates: ${candidates.length})`
269
+ return formatMemoryResults(candidates)
270
+ }),
271
+
272
+ searchAgentMemories: Effect.fn('MemoryService.searchAgentMemories')(function* (
273
+ orgId: string,
274
+ agentName: string,
275
+ query: string,
276
+ ) {
277
+ if (!isRoutableAgentName(agentName)) {
278
+ aiLogger.debug`Agent memory search skipped - invalid agentName: ${agentName}`
279
+ return 'No stored memories.'
280
+ }
281
+
282
+ const scoped = agentScopeId(orgId, agentName)
283
+ aiLogger.debug`Agent memory search requested (orgId: ${orgId}, agentName: ${agentName}, scopeId: ${scoped}, queryLength: ${query.length})`
284
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
285
+ const results = yield* searchMemoriesEffect({
286
+ runtimeConfig,
287
+ helperModelRuntime,
288
+ resolveReranker: resolveRerankService,
289
+ query,
290
+ memory,
291
+ scopeId: scoped,
292
+ memoryType: ORG_MEMORY_TYPE,
293
+ })
294
+ aiLogger.debug`Agent memory search completed (agentName: ${agentName}, resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
295
+ return results
296
+ }),
297
+
298
+ listOrganizationMemoryRecords: Effect.fn('MemoryService.listOrganizationMemoryRecords')(function* (params: {
299
+ orgId: string
300
+ limit?: number
301
+ memoryType?: MemoryType
302
+ metadataEquals?: Record<string, MemoryListScalar>
303
+ metadataNotEquals?: Record<string, MemoryListScalar>
304
+ sort?: 'createdAtAsc' | 'createdAtDesc'
305
+ }) {
306
+ const { orgId, ...listOptions } = params
307
+ const orgMemory = yield* getOrgMemory(orgMemoryCache, orgId)
308
+ return yield* memoryTryPromise('Failed to list organization memory records.', () =>
309
+ orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions }),
310
+ )
311
+ }),
312
+
313
+ getTopMemories(params: { orgId: string; agentName?: string; limit?: number }) {
314
+ return getTopMemoriesSection({
315
+ orgMemoryCache,
316
+ orgId: params.orgId,
317
+ agentName: params.agentName,
318
+ limit: params.limit,
319
+ })
320
+ },
321
+
322
+ searchAllMemoriesBatched: Effect.fn('MemoryService.searchAllMemoriesBatched')(function* ({
323
+ orgId,
324
+ agentName,
325
+ query,
326
+ fastMode = true,
327
+ allowMultiScopeRerank = true,
328
+ }: {
329
+ orgId: string
330
+ agentName?: string
331
+ query: string
332
+ fastMode?: boolean
333
+ allowMultiScopeRerank?: boolean
334
+ }) {
335
+ const limit = Math.min(runtimeConfig.memory.searchK, MAX_MEMORY_RESULTS_PER_SCOPE)
336
+ const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
337
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
338
+ const orgMemory = yield* getOrgMemory(orgMemoryCache, orgId)
339
+
340
+ const retrievalTasks = [
341
+ {
342
+ scopeTag: 'org',
343
+ retrieve: () =>
344
+ orgMemory.searchCandidates(query, {
345
+ scopeId: orgScopeId,
346
+ limit: candidateLimit,
347
+ memoryType: ORG_MEMORY_TYPE,
348
+ fastMode,
349
+ includeNeighborContext: !fastMode,
350
+ }),
351
+ },
352
+ ]
353
+
354
+ if (isRoutableAgentName(agentName)) {
355
+ const agentScoped = agentScopeId(orgId, agentName)
356
+ retrievalTasks.push({
357
+ scopeTag: `agent:${agentName}`,
358
+ retrieve: () =>
359
+ orgMemory.searchCandidates(query, {
360
+ scopeId: agentScoped,
361
+ limit: candidateLimit,
362
+ memoryType: ORG_MEMORY_TYPE,
363
+ fastMode,
364
+ includeNeighborContext: !fastMode,
365
+ }),
366
+ })
367
+ }
368
+
369
+ const results = yield* memoryTryPromise('Failed to execute scoped memory retrieval.', () =>
370
+ executeScopedRetrieval<MemorySearchResult>(retrievalTasks),
371
+ )
372
+ const totalCandidates = countScopedRetrievalCandidates(results)
373
+ aiLogger.debug`Batched memory search candidates (scopes: ${results.length}, total: ${totalCandidates})`
374
+
375
+ if (totalCandidates === 0) {
376
+ return 'No stored memories.'
377
+ }
378
+
379
+ const candidatesByScopeTag = scopedRetrievalToMap(results)
380
+ if (fastMode || !allowMultiScopeRerank) {
381
+ return formatBatchedResults(null, candidatesByScopeTag, limit, agentName)
382
+ }
383
+
384
+ const reranked = yield* rerankCandidatesMultiScope(
385
+ runtimeConfig,
386
+ helperModelRuntime,
387
+ query,
388
+ results,
389
+ limit,
390
+ resolveRerankService(),
391
+ )
392
+ return formatBatchedResults(reranked, candidatesByScopeTag, limit, agentName)
393
+ }),
394
+
395
+ createOrganizationMemory({
396
+ orgId,
397
+ content,
398
+ memoryType,
399
+ metadata,
400
+ importance,
401
+ durability,
402
+ }: {
403
+ orgId: string
404
+ content: string
405
+ memoryType: MemoryType
406
+ metadata?: Record<string, unknown>
407
+ importance?: number
408
+ durability?: MemoryRecord['durability']
409
+ }) {
410
+ return Effect.gen(function* () {
411
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
412
+ return yield* memoryTryPromise('Failed to create organization memory.', () =>
413
+ memory.insert(content, {
414
+ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId),
415
+ memoryType,
416
+ importance: importance ?? 1,
417
+ durability,
418
+ metadata: { orgId, ...metadata },
419
+ }),
420
+ )
421
+ }).pipe(
422
+ Effect.withSpan('MemoryService.createOrganizationMemory'),
423
+ Effect.tap(() =>
424
+ Effect.sync(() => {
425
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
426
+ aiLogger.debug`createOrganizationMemory - orgId: "${orgId}", scopeId: "${orgScopeId}", content preview: "${content.slice(0, 50)}"`
427
+ }),
428
+ ),
429
+ Effect.catch((error) => {
430
+ if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
431
+ aiLogger.debug`Organization memory already exists (hash conflict)`
432
+ return Effect.succeed('')
433
+ }
434
+ return Effect.fail(error)
435
+ }),
436
+ )
437
+ },
438
+
439
+ addOrganizationMemoryRelation: Effect.fn('MemoryService.addOrganizationMemoryRelation')(function* ({
440
+ orgId,
441
+ fromMemoryId,
442
+ toMemoryId,
443
+ relationType,
444
+ confidence,
445
+ }: {
446
+ orgId: string
447
+ fromMemoryId: string
448
+ toMemoryId: string
449
+ relationType: RelationType
450
+ confidence?: number
451
+ }) {
452
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
453
+ return yield* memoryTryPromise('Failed to add organization memory relation.', () =>
454
+ memory.addRelation(fromMemoryId, toMemoryId, relationType, confidence),
455
+ )
456
+ }),
457
+
458
+ createAgentMemory({
459
+ orgId,
460
+ agentName,
461
+ content,
462
+ memoryType,
463
+ metadata,
464
+ importance,
465
+ }: {
466
+ orgId: string
467
+ agentName: string
468
+ content: string
469
+ memoryType: MemoryType
470
+ metadata?: Record<string, unknown>
471
+ importance?: number
472
+ }) {
473
+ if (!isRoutableAgentName(agentName)) {
474
+ return Effect.fail(new InvalidAgentNameError({ agentName }))
475
+ }
476
+
477
+ const scoped = agentScopeId(orgId, agentName)
478
+ return Effect.gen(function* () {
479
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
480
+ return yield* memoryTryPromise('Failed to create agent memory.', () =>
481
+ memory.insert(content, {
482
+ scopeId: scoped,
483
+ memoryType,
484
+ importance: importance ?? 1,
485
+ metadata: { orgId, agentName, memoryScope: 'agent', ...metadata },
486
+ }),
487
+ )
488
+ }).pipe(
489
+ Effect.withSpan('MemoryService.createAgentMemory'),
490
+ Effect.catch((error) => {
491
+ if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
492
+ aiLogger.debug`Agent memory already exists (hash conflict)`
493
+ return Effect.succeed('')
494
+ }
495
+ return Effect.fail(error)
496
+ }),
497
+ )
498
+ },
499
+
500
+ assessMemoryCandidate: Effect.fn('MemoryService.assessMemoryCandidate')(function* (params: {
501
+ orgId: string
502
+ content: string
503
+ }) {
504
+ const trimmed = compactWhitespace(params.content)
505
+ if (!trimmed) return null
506
+
507
+ const memory = yield* getOrgMemory(orgMemoryCache, params.orgId)
508
+ const facts = yield* memoryTryPromise('Failed to assess direct memory candidate.', () =>
509
+ memory.extractFactsFromMessages([{ role: 'user', content: trimmed }], {
510
+ maxFacts: 1,
511
+ customPrompt: DIRECT_MEMORY_ASSESSMENT_PROMPT,
512
+ }),
513
+ )
514
+ if (facts.length === 0) return null
515
+ const fact = facts[0]
516
+
517
+ return {
518
+ classification: fact.classification,
519
+ durability: fact.durability,
520
+ importance: clampImportance(fact.importance),
521
+ rationale: fact.rationale,
522
+ }
523
+ }),
524
+
525
+ updateOrganizationMemoryById: Effect.fn('MemoryService.updateOrganizationMemoryById')(function* ({
526
+ orgId,
527
+ memoryId,
528
+ content,
529
+ }: {
530
+ orgId: string
531
+ memoryId: string
532
+ content: string
533
+ }) {
534
+ const memory = yield* getOrgMemory(orgMemoryCache, orgId)
535
+ return yield* memoryTryPromise('Failed to update organization memory.', () =>
536
+ memory.updateMemory(memoryId, content),
537
+ )
538
+ }),
539
+
540
+ addExtractedFactsToScopes: Effect.fn('MemoryService.addExtractedFactsToScopes')(function* (params: {
541
+ orgId: string
542
+ facts: ExtractedFact[]
543
+ source: string
544
+ sourceMetadata?: Record<string, unknown>
545
+ agentNames?: string[]
546
+ acquireLock?: boolean
547
+ }) {
548
+ if (params.facts.length === 0) return
549
+
550
+ const orgMemory = yield* getOrgMemory(orgMemoryCache, params.orgId)
551
+ const scopes: AddOptions[] = [
552
+ {
553
+ scopeId: scopeId(ORG_SCOPE_PREFIX, params.orgId),
554
+ memoryType: ORG_MEMORY_TYPE,
555
+ metadata: { orgId: params.orgId, source: params.source, ...params.sourceMetadata },
556
+ },
557
+ ]
558
+
559
+ for (const scopedAgentName of resolveAgentScopeNames(undefined, params.agentNames ?? [])) {
560
+ scopes.push({
561
+ scopeId: agentScopeId(params.orgId, scopedAgentName),
562
+ memoryType: ORG_MEMORY_TYPE,
563
+ metadata: {
564
+ orgId: params.orgId,
565
+ source: params.source,
566
+ ...params.sourceMetadata,
567
+ agentName: scopedAgentName,
568
+ memoryScope: 'agent',
569
+ },
570
+ })
571
+ }
572
+
573
+ const preparedUpdates = yield* memoryTryPromise('Failed to prepare extracted facts for memory scopes.', () =>
574
+ orgMemory.prepareFactsToScopes(params.facts, scopes),
575
+ )
576
+ yield* applyPreparedScopeUpdatesEffect({
577
+ orgId: params.orgId,
578
+ orgMemory,
579
+ preparedUpdates,
580
+ acquireLock: params.acquireLock,
581
+ })
582
+ }),
583
+
584
+ addConversationMemories: Effect.fn('MemoryService.addConversationMemories')(function* ({
585
+ orgId,
586
+ input,
587
+ output,
588
+ sourceId,
589
+ source = 'chat',
590
+ sourceMetadata,
591
+ onboardStatus,
592
+ agentName,
593
+ historyMessages = [],
594
+ memoryBlock,
595
+ attachmentContext,
596
+ agentNames = [],
597
+ }: {
598
+ orgId: string
599
+ input: string
600
+ output: string
601
+ sourceId?: string
602
+ source?: string
603
+ sourceMetadata?: Record<string, unknown>
604
+ onboardStatus?: string
605
+ agentName?: string
606
+ historyMessages?: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
607
+ memoryBlock?: string
608
+ attachmentContext?: string
609
+ agentNames?: string[]
610
+ }) {
611
+ const { messages, normalizedInput, sanitizedOutput } = buildConversationMessages({
612
+ input,
613
+ output,
614
+ historyMessages,
615
+ memoryBlock,
616
+ attachmentContext,
617
+ })
618
+
619
+ if (!normalizedInput || !sanitizedOutput || messages.length === 0) {
620
+ aiLogger.debug`Skipping memory add - empty input or output`
621
+ return
622
+ }
623
+
624
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
625
+ aiLogger.debug`addConversationMemories - orgId: "${orgId}", scopeId: "${orgScopeId}", sourceId: ${sourceId ?? 'none'}`
626
+
627
+ const onboardingActive = onboardStatus !== undefined && onboardStatus !== 'completed'
628
+ const extractionConfig = onboardingActive
629
+ ? { maxFacts: ONBOARDING_MEMORY_MAX_FACTS, customPrompt: ONBOARDING_MEMORY_EXTRACTION_PROMPT }
630
+ : undefined
631
+ const orgMemory = yield* getOrgMemory(orgMemoryCache, orgId)
632
+
633
+ const scopes: AddOptions[] = [
634
+ {
635
+ scopeId: orgScopeId,
636
+ memoryType: ORG_MEMORY_TYPE,
637
+ metadata: { orgId, source, ...(sourceId ? { sourceId } : {}), ...sourceMetadata },
638
+ },
639
+ ]
640
+
641
+ for (const scopedAgentName of resolveAgentScopeNames(agentName, agentNames)) {
642
+ const agentId = agentScopeId(orgId, scopedAgentName)
643
+ scopes.push({
644
+ scopeId: agentId,
645
+ memoryType: ORG_MEMORY_TYPE,
646
+ metadata: {
647
+ orgId,
648
+ agentName: scopedAgentName,
649
+ memoryScope: 'agent',
650
+ source,
651
+ ...(sourceId ? { sourceId } : {}),
652
+ ...sourceMetadata,
653
+ },
654
+ })
655
+ }
656
+
657
+ const extractedFacts = yield* memoryTryPromise('Failed to extract conversation memories.', () =>
658
+ orgMemory.extractFactsFromMessages(messages, extractionConfig),
659
+ )
660
+ if (shouldSkipExtractedFacts(extractedFacts)) {
661
+ aiLogger.debug`Skipping transient conversation memory`
662
+ return
663
+ }
664
+
665
+ const preparedUpdates = yield* memoryTryPromise('Failed to prepare conversation memories for scopes.', () =>
666
+ orgMemory.prepareFactsToScopes(extractedFacts, scopes),
667
+ )
668
+ yield* applyPreparedScopeUpdatesEffect({ orgId, orgMemory, preparedUpdates })
669
+ if (preparedUpdates.length > 0) {
670
+ aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
671
+ }
672
+ }),
673
+ }
674
+
675
+ return service
676
+ }
677
+
678
+ export class MemoryServiceTag extends Context.Service<MemoryServiceTag, ReturnType<typeof createMemoryService>>()(
679
+ 'MemoryService',
680
+ ) {}
681
+
682
+ export const MemoryServiceLive = Layer.effect(
683
+ MemoryServiceTag,
684
+ Effect.gen(function* () {
685
+ const db = yield* DatabaseServiceTag
686
+ const runtimeConfig = yield* RuntimeConfigServiceTag
687
+ const rerankService = yield* RerankServiceTag
688
+ const helperModelRuntime = yield* HelperModelTag
689
+ const orgMemoryCache = yield* makeOrgMemoryCache({ db, runtimeConfig, helperModelRuntime })
690
+ return createMemoryService({ runtimeConfig, rerankService, helperModelRuntime, orgMemoryCache })
691
+ }),
692
+ )