@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
@@ -0,0 +1,95 @@
1
+ import { getAgentRoster } from '../../config/agent-defaults'
2
+ import type { ExtractedFact, Message } from '../../db/memory-types'
3
+ import { sanitizeAgentOutputForMemory } from '../../runtime/llm-content'
4
+ import { compactWhitespace, truncateText } from '../../utils/string'
5
+
6
+ const MAX_CONVERSATION_HISTORY_MESSAGES = 24
7
+ const MAX_CONVERSATION_MESSAGE_CHARS = 1_200
8
+ const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
9
+ const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
10
+ const LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD = 0.45
11
+
12
+ export const isRoutableAgentName = (value?: string): value is string =>
13
+ Boolean(value && getAgentRoster().includes(value))
14
+
15
+ function normalizeConversationText(value: string, maxChars: number): string {
16
+ const normalized = compactWhitespace(value)
17
+ if (!normalized) return ''
18
+ return truncateText(normalized, maxChars)
19
+ }
20
+
21
+ export function resolveAgentScopeNames(agentName?: string, agentNames: string[] = []): string[] {
22
+ const unique = new Set<string>()
23
+ if (typeof agentName === 'string' && agentName.trim()) {
24
+ unique.add(agentName.trim())
25
+ }
26
+ for (const candidate of agentNames) {
27
+ if (typeof candidate !== 'string') continue
28
+ const normalized = candidate.trim()
29
+ if (!normalized) continue
30
+ unique.add(normalized)
31
+ }
32
+ return [...unique].filter((name) => isRoutableAgentName(name))
33
+ }
34
+
35
+ export function buildConversationMessages(params: {
36
+ input: string
37
+ output: string
38
+ historyMessages: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
39
+ memoryBlock?: string
40
+ attachmentContext?: string
41
+ }): { messages: Message[]; normalizedInput: string; sanitizedOutput: string } {
42
+ const normalizedInput = normalizeConversationText(params.input, MAX_CONVERSATION_MESSAGE_CHARS)
43
+ const sanitizedOutput = normalizeConversationText(
44
+ sanitizeAgentOutputForMemory(params.output),
45
+ MAX_CONVERSATION_MESSAGE_CHARS,
46
+ )
47
+ if (!normalizedInput || !sanitizedOutput) {
48
+ return { messages: [], normalizedInput, sanitizedOutput }
49
+ }
50
+
51
+ const messages: Message[] = []
52
+
53
+ const normalizedHistory: Message[] = params.historyMessages
54
+ .map((message): Message | null => {
55
+ const role: Message['role'] = message.role === 'agent' ? 'agent' : 'user'
56
+ const normalized = normalizeConversationText(
57
+ role === 'agent' ? sanitizeAgentOutputForMemory(message.content) : message.content,
58
+ MAX_CONVERSATION_MESSAGE_CHARS,
59
+ )
60
+ if (!normalized) return null
61
+ return { role, content: normalized }
62
+ })
63
+ .filter((message): message is Message => message !== null)
64
+ .slice(-MAX_CONVERSATION_HISTORY_MESSAGES)
65
+
66
+ messages.push(...normalizedHistory)
67
+
68
+ const normalizedMemoryBlock = normalizeConversationText(params.memoryBlock ?? '', MAX_CONVERSATION_MEMORY_BLOCK_CHARS)
69
+ if (normalizedMemoryBlock) {
70
+ messages.push({ role: 'user', content: `Thread memory block:\n${normalizedMemoryBlock}` })
71
+ }
72
+
73
+ const normalizedAttachmentContext = normalizeConversationText(
74
+ params.attachmentContext ?? '',
75
+ MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS,
76
+ )
77
+ if (normalizedAttachmentContext) {
78
+ messages.push({ role: 'user', content: `Attachment context:\n${normalizedAttachmentContext}` })
79
+ }
80
+
81
+ messages.push({ role: 'user', content: normalizedInput })
82
+ messages.push({ role: 'agent', content: sanitizedOutput })
83
+
84
+ return { messages, normalizedInput, sanitizedOutput }
85
+ }
86
+
87
+ export function shouldSkipExtractedFacts(facts: ExtractedFact[]): boolean {
88
+ if (facts.length === 0) return true
89
+ return facts.every(
90
+ (fact) =>
91
+ fact.durability === 'ephemeral' &&
92
+ fact.importance <= LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD &&
93
+ fact.classification !== 'durable',
94
+ )
95
+ }
@@ -0,0 +1,39 @@
1
+ import { Cache, Duration, Effect } from 'effect'
2
+
3
+ import { aiLogger } from '../../config/logger'
4
+ import { Memory } from '../../db/memory'
5
+ import type { SurrealDBService } from '../../db/service'
6
+ import type { HelperModelRuntime } from '../../runtime/helper-model'
7
+ import { ORG_SCOPE_PREFIX, scopeId } from '../../runtime/memory/memory-scope'
8
+ import type { ResolvedLotaRuntimeConfig } from '../../runtime/runtime-config'
9
+ import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../../system-agents/memory.agent'
10
+
11
+ const MAX_ORG_MEMORY_CLIENTS = 128
12
+
13
+ interface OrgMemoryDeps {
14
+ db: SurrealDBService
15
+ runtimeConfig: ResolvedLotaRuntimeConfig
16
+ helperModelRuntime: HelperModelRuntime
17
+ }
18
+
19
+ export type OrgMemoryCache = Cache.Cache<string, Memory>
20
+
21
+ export function makeOrgMemoryCache(deps: OrgMemoryDeps) {
22
+ return Cache.make({
23
+ lookup: (cacheKey: string) =>
24
+ Effect.sync(() => {
25
+ aiLogger.debug`Memory client created and cached for ${cacheKey}`
26
+ return new Memory(
27
+ { db: deps.db, runtimeConfig: deps.runtimeConfig, helperModelRuntime: deps.helperModelRuntime },
28
+ { createAgent: createOrgMemoryAgent },
29
+ { customPrompt: ORG_MEMORY_PROMPT },
30
+ )
31
+ }),
32
+ capacity: MAX_ORG_MEMORY_CLIENTS,
33
+ timeToLive: Duration.infinity,
34
+ })
35
+ }
36
+
37
+ export function getOrgMemory(cache: OrgMemoryCache, orgId: string): Effect.Effect<Memory> {
38
+ return Cache.get(cache, scopeId(ORG_SCOPE_PREFIX, orgId))
39
+ }
@@ -0,0 +1,80 @@
1
+ import { Effect } from 'effect'
2
+
3
+ import type { MemoryRecord } from '../../db/memory-types'
4
+ import { ORG_SCOPE_PREFIX, agentScopeId, scopeId } from '../../runtime/memory/memory-scope'
5
+ import { compactWhitespace, truncateText } from '../../utils/string'
6
+ import type { OrgMemoryCache } from './memory-org-memory'
7
+ import { getOrgMemory } from './memory-org-memory'
8
+
9
+ const PRESEEDED_MEMORY_LIMIT = 5
10
+ const PRESEEDED_MEMORY_MAX_CHARS = 300
11
+ const PRESEEDED_MIN_IMPORTANCE = 0.7
12
+ const PRESEEDED_MEMORY_DURABILITY: MemoryRecord['durability'] = 'core'
13
+
14
+ function normalizePreSeededMemoryText(value: string): string {
15
+ return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
16
+ }
17
+
18
+ function formatPreSeededMemoriesSection(memories: MemoryRecord[]): string | undefined {
19
+ const lines = memories
20
+ .map((memory) => normalizePreSeededMemoryText(memory.content))
21
+ .filter((line) => line.length > 0)
22
+ .map((line) => `- ${line}`)
23
+ if (lines.length === 0) return undefined
24
+
25
+ return ['<pre-seeded-memories>', ...lines, '</pre-seeded-memories>'].join('\n')
26
+ }
27
+
28
+ export function getTopMemoriesSection(params: {
29
+ orgMemoryCache: OrgMemoryCache
30
+ orgId: string
31
+ agentName?: string
32
+ limit?: number
33
+ }): Effect.Effect<string | undefined, unknown> {
34
+ return Effect.gen(function* () {
35
+ const orgMemory = yield* getOrgMemory(params.orgMemoryCache, params.orgId)
36
+ const orgScopeId = scopeId(ORG_SCOPE_PREFIX, params.orgId)
37
+ const requestedLimit = params.limit ?? PRESEEDED_MEMORY_LIMIT
38
+ const limit = Math.max(1, Math.min(requestedLimit, PRESEEDED_MEMORY_LIMIT))
39
+
40
+ const [orgTopMemories, agentTopMemories] = yield* Effect.all([
41
+ orgMemory.listTopMemories({
42
+ scopeId: orgScopeId,
43
+ limit,
44
+ memoryType: 'fact',
45
+ durability: PRESEEDED_MEMORY_DURABILITY,
46
+ minImportance: PRESEEDED_MIN_IMPORTANCE,
47
+ }),
48
+ params.agentName && params.agentName.trim()
49
+ ? orgMemory.listTopMemories({
50
+ scopeId: agentScopeId(params.orgId, params.agentName),
51
+ limit,
52
+ memoryType: 'fact',
53
+ durability: PRESEEDED_MEMORY_DURABILITY,
54
+ minImportance: PRESEEDED_MIN_IMPORTANCE,
55
+ })
56
+ : Effect.succeed([] as MemoryRecord[]),
57
+ ])
58
+
59
+ const combined = [...agentTopMemories, ...orgTopMemories].sort((left, right) => {
60
+ if (right.importance !== left.importance) return right.importance - left.importance
61
+ if (right.accessCount !== left.accessCount) return right.accessCount - left.accessCount
62
+ const rightLastAccess = right.lastAccessedAt?.getTime() ?? 0
63
+ const leftLastAccess = left.lastAccessedAt?.getTime() ?? 0
64
+ if (rightLastAccess !== leftLastAccess) return rightLastAccess - leftLastAccess
65
+ return right.createdAt.getTime() - left.createdAt.getTime()
66
+ })
67
+
68
+ const deduped: MemoryRecord[] = []
69
+ const seen = new Set<string>()
70
+ for (const memory of combined) {
71
+ const normalizedKey = compactWhitespace(memory.content).toLowerCase()
72
+ if (!normalizedKey || seen.has(normalizedKey)) continue
73
+ seen.add(normalizedKey)
74
+ deduped.push(memory)
75
+ if (deduped.length >= limit) break
76
+ }
77
+
78
+ return formatPreSeededMemoriesSection(deduped)
79
+ })
80
+ }
@@ -0,0 +1,297 @@
1
+ import { Effect, Schema } from 'effect'
2
+ import { z } from 'zod'
3
+
4
+ import { aiLogger } from '../../config/logger'
5
+ import type { MemorySearchResult } from '../../db/memory-types'
6
+ import type { HelperModelRuntime } from '../../runtime/helper-model'
7
+ import type { MemoryRerankerStrategy, ResolvedLotaRuntimeConfig } from '../../runtime/runtime-config'
8
+ import { createMemoryRerankerAgent, MEMORY_RERANKER_PROMPT } from '../../system-agents/memory-reranker.agent'
9
+ import { compactWhitespace, truncateText } from '../../utils/string'
10
+ import type { makeRerankService } from './rerank.service'
11
+
12
+ const RERANK_CANDIDATE_MAX_CHARS = 500
13
+ const RERANK_SECTION_TITLE = 'Most relevant memories'
14
+
15
+ const RERANK_PROMPT_CANDIDATE_SCHEMA = Schema.Struct({ id: Schema.String, text: Schema.String, score: Schema.Number })
16
+
17
+ const RERANK_PROMPT_MULTI_SCOPE_CANDIDATE_SCHEMA = Schema.Struct({
18
+ id: Schema.String,
19
+ text: Schema.String,
20
+ score: Schema.Number,
21
+ scope: Schema.String,
22
+ })
23
+
24
+ const RERANK_PROMPT_SCHEMA = Schema.Struct({
25
+ query: Schema.String,
26
+ maxItems: Schema.Number,
27
+ candidates: Schema.Array(RERANK_PROMPT_CANDIDATE_SCHEMA),
28
+ })
29
+
30
+ const RERANK_MULTI_SCOPE_PROMPT_SCHEMA = Schema.Struct({
31
+ query: Schema.String,
32
+ maxItems: Schema.Number,
33
+ candidates: Schema.Array(RERANK_PROMPT_MULTI_SCOPE_CANDIDATE_SCHEMA),
34
+ })
35
+
36
+ const stringifyRerankPrompt = Schema.encodeSync(Schema.fromJsonString(RERANK_PROMPT_SCHEMA))
37
+ const stringifyRerankMultiScopePrompt = Schema.encodeSync(Schema.fromJsonString(RERANK_MULTI_SCOPE_PROMPT_SCHEMA))
38
+
39
+ class MemoryRerankError extends Schema.TaggedErrorClass<MemoryRerankError>()('MemoryRerankError', {
40
+ scope: Schema.Literals(['single', 'multi-scope']),
41
+ message: Schema.String,
42
+ cause: Schema.optional(Schema.Unknown),
43
+ }) {}
44
+
45
+ const MemoryRerankOutputSchema = z.object({
46
+ sections: z.array(
47
+ z.object({
48
+ title: z.string(),
49
+ items: z.array(
50
+ z.object({
51
+ id: z.string(),
52
+ relevance: z.string().describe('Short relevance reason. Use empty string when no reason is needed.'),
53
+ }),
54
+ ),
55
+ }),
56
+ ),
57
+ })
58
+
59
+ export type MemoryRerankOutput = z.infer<typeof MemoryRerankOutputSchema>
60
+
61
+ function truncateCandidateText(value: string): string {
62
+ return truncateText(compactWhitespace(value), RERANK_CANDIDATE_MAX_CHARS)
63
+ }
64
+
65
+ function readNumericMetadata(metadata: Record<string, unknown>, key: string): number {
66
+ const value = metadata[key]
67
+ if (typeof value === 'number' && Number.isFinite(value)) return value
68
+ return 0
69
+ }
70
+
71
+ function buildRerankerCandidateText(candidate: MemorySearchResult): string {
72
+ const metadata = candidate.metadata
73
+ const relationCount = readNumericMetadata(metadata, 'relationCount')
74
+ const supportCount = readNumericMetadata(metadata, 'supportCount')
75
+ const contradictCount = readNumericMetadata(metadata, 'contradictCount')
76
+
77
+ const relatedContextRaw = metadata.relatedContext
78
+ const relatedContext = Array.isArray(relatedContextRaw)
79
+ ? relatedContextRaw
80
+ .slice(0, 3)
81
+ .map((item) => String(item).trim())
82
+ .filter((item) => item.length > 0)
83
+ : []
84
+
85
+ const contextLines: string[] = []
86
+ if (relatedContext.length > 0) {
87
+ contextLines.push(`Related context: ${relatedContext.join(' | ')}`)
88
+ }
89
+ if (relationCount > 0 || supportCount > 0 || contradictCount > 0) {
90
+ contextLines.push(
91
+ `Graph signals: relations=${relationCount}; supports=${supportCount}; contradicts=${contradictCount}`,
92
+ )
93
+ }
94
+
95
+ if (contextLines.length === 0) return candidate.content
96
+ return `${candidate.content}\n\n${contextLines.join('\n')}`
97
+ }
98
+
99
+ function getRerankerStrategy(runtimeConfig: ResolvedLotaRuntimeConfig): MemoryRerankerStrategy {
100
+ return runtimeConfig.memory.rerankerStrategy
101
+ }
102
+
103
+ function buildRerankOutput(ids: string[], title = RERANK_SECTION_TITLE): MemoryRerankOutput {
104
+ return { sections: [{ title, items: ids.map((id) => ({ id, relevance: '' })) }] }
105
+ }
106
+
107
+ function toRerankError(scope: 'single' | 'multi-scope', message: string, cause?: unknown): MemoryRerankError {
108
+ return new MemoryRerankError({ scope, message, cause })
109
+ }
110
+
111
+ function logRerankError(scope: 'single' | 'multi-scope', error: unknown): void {
112
+ if (scope === 'multi-scope') {
113
+ aiLogger.error`Multi-scope memory reranker failed: ${error}`
114
+ return
115
+ }
116
+
117
+ aiLogger.error`Memory reranker failed: ${error}`
118
+ }
119
+
120
+ function rerankCandidatesWithHelperEffect(
121
+ helperModelRuntime: HelperModelRuntime,
122
+ query: string,
123
+ candidates: MemorySearchResult[],
124
+ maxItems: number,
125
+ ): Effect.Effect<MemoryRerankOutput, MemoryRerankError> {
126
+ return Effect.tryPromise({
127
+ try: () =>
128
+ helperModelRuntime.generateHelperStructured({
129
+ tag: 'memory-reranker',
130
+ createAgent: createMemoryRerankerAgent,
131
+ defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
132
+ messages: [
133
+ {
134
+ role: 'user',
135
+ content: stringifyRerankPrompt({
136
+ query,
137
+ maxItems,
138
+ candidates: candidates.map((candidate) => ({
139
+ id: candidate.id,
140
+ text: truncateCandidateText(buildRerankerCandidateText(candidate)),
141
+ score: candidate.score,
142
+ })),
143
+ }),
144
+ },
145
+ ],
146
+ schema: MemoryRerankOutputSchema,
147
+ }),
148
+ catch: (cause) => {
149
+ logRerankError('single', cause)
150
+ return toRerankError('single', 'Memory reranker failed.', cause)
151
+ },
152
+ })
153
+ }
154
+
155
+ function rerankCandidatesWithRerankServiceEffect(
156
+ query: string,
157
+ candidates: MemorySearchResult[],
158
+ maxItems: number,
159
+ reranker: ReturnType<typeof makeRerankService>,
160
+ ): Effect.Effect<MemoryRerankOutput, MemoryRerankError> {
161
+ return reranker
162
+ .rerankDocuments({
163
+ query,
164
+ topN: maxItems,
165
+ documents: candidates.map((candidate) => ({
166
+ id: candidate.id,
167
+ text: truncateCandidateText(buildRerankerCandidateText(candidate)),
168
+ })),
169
+ })
170
+ .pipe(
171
+ Effect.mapError((cause) => {
172
+ logRerankError('single', cause)
173
+ return toRerankError('single', 'Memory reranker failed.', cause)
174
+ }),
175
+ Effect.map((reranked) => buildRerankOutput(reranked.results.map((item: { id: string }) => item.id))),
176
+ )
177
+ }
178
+
179
+ export function rerankCandidates(
180
+ runtimeConfig: ResolvedLotaRuntimeConfig,
181
+ helperModelRuntime: HelperModelRuntime,
182
+ query: string,
183
+ candidates: MemorySearchResult[],
184
+ maxItems: number,
185
+ reranker?: ReturnType<typeof makeRerankService>,
186
+ ): Effect.Effect<MemoryRerankOutput | null> {
187
+ if (candidates.length === 0) return Effect.succeed(null)
188
+
189
+ return Effect.gen(function* () {
190
+ if (getRerankerStrategy(runtimeConfig) === 'rerank' && reranker) {
191
+ return yield* rerankCandidatesWithRerankServiceEffect(query, candidates, maxItems, reranker)
192
+ }
193
+
194
+ return yield* rerankCandidatesWithHelperEffect(helperModelRuntime, query, candidates, maxItems)
195
+ }).pipe(
196
+ Effect.catchCause((cause) =>
197
+ Effect.sync(() => {
198
+ logRerankError('single', cause)
199
+ return null
200
+ }),
201
+ ),
202
+ )
203
+ }
204
+
205
+ function rerankCandidatesMultiScopeWithHelperEffect(
206
+ helperModelRuntime: HelperModelRuntime,
207
+ query: string,
208
+ flattenedCandidates: Array<MemorySearchResult & { scopeTag: string }>,
209
+ maxItems: number,
210
+ ): Effect.Effect<MemoryRerankOutput, MemoryRerankError> {
211
+ return Effect.tryPromise({
212
+ try: () =>
213
+ helperModelRuntime.generateHelperStructured({
214
+ tag: 'memory-reranker-multi-scope',
215
+ createAgent: createMemoryRerankerAgent,
216
+ defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
217
+ messages: [
218
+ {
219
+ role: 'user',
220
+ content: stringifyRerankMultiScopePrompt({
221
+ query,
222
+ maxItems,
223
+ candidates: flattenedCandidates.map((candidate) => ({
224
+ id: candidate.id,
225
+ text: truncateCandidateText(buildRerankerCandidateText(candidate)),
226
+ score: candidate.score,
227
+ scope: candidate.scopeTag,
228
+ })),
229
+ }),
230
+ },
231
+ ],
232
+ schema: MemoryRerankOutputSchema,
233
+ }),
234
+ catch: (cause) => {
235
+ logRerankError('multi-scope', cause)
236
+ return toRerankError('multi-scope', 'Multi-scope memory reranker failed.', cause)
237
+ },
238
+ })
239
+ }
240
+
241
+ function rerankCandidatesMultiScopeWithRerankServiceEffect(
242
+ query: string,
243
+ flattenedCandidates: Array<MemorySearchResult & { scopeTag: string }>,
244
+ maxItems: number,
245
+ reranker: ReturnType<typeof makeRerankService>,
246
+ ): Effect.Effect<MemoryRerankOutput, MemoryRerankError> {
247
+ return reranker
248
+ .rerankDocuments({
249
+ query,
250
+ topN: maxItems,
251
+ documents: flattenedCandidates.map((candidate) => ({
252
+ id: candidate.id,
253
+ text: truncateCandidateText(`${buildRerankerCandidateText(candidate)}\n\nScope: ${candidate.scopeTag}`),
254
+ })),
255
+ })
256
+ .pipe(
257
+ Effect.mapError((cause) => {
258
+ logRerankError('multi-scope', cause)
259
+ return toRerankError('multi-scope', 'Multi-scope memory reranker failed.', cause)
260
+ }),
261
+ Effect.map((reranked) =>
262
+ buildRerankOutput(
263
+ reranked.results.map((item: { id: string }) => item.id),
264
+ 'Top matches across memory scopes',
265
+ ),
266
+ ),
267
+ )
268
+ }
269
+
270
+ export function rerankCandidatesMultiScope(
271
+ runtimeConfig: ResolvedLotaRuntimeConfig,
272
+ helperModelRuntime: HelperModelRuntime,
273
+ query: string,
274
+ scopedCandidates: Array<{ scopeTag: string; candidates: MemorySearchResult[] }>,
275
+ maxItems: number,
276
+ reranker?: ReturnType<typeof makeRerankService>,
277
+ ): Effect.Effect<MemoryRerankOutput | null> {
278
+ const flattened = scopedCandidates.flatMap(({ scopeTag, candidates }) =>
279
+ candidates.map((candidate) => ({ ...candidate, scopeTag })),
280
+ )
281
+ if (flattened.length === 0 || flattened.length <= maxItems) return Effect.succeed(null)
282
+
283
+ return Effect.gen(function* () {
284
+ if (getRerankerStrategy(runtimeConfig) === 'rerank' && reranker) {
285
+ return yield* rerankCandidatesMultiScopeWithRerankServiceEffect(query, flattened, maxItems, reranker)
286
+ }
287
+
288
+ return yield* rerankCandidatesMultiScopeWithHelperEffect(helperModelRuntime, query, flattened, maxItems)
289
+ }).pipe(
290
+ Effect.catchCause((cause) =>
291
+ Effect.sync(() => {
292
+ logRerankError('multi-scope', cause)
293
+ return null
294
+ }),
295
+ ),
296
+ )
297
+ }
@@ -1,8 +1,8 @@
1
- import { MEMORY } from '../config/constants'
2
- import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../config/search'
3
- import type { MemorySearchResult } from '../db/memory-types'
4
- import { compactWhitespace, truncateText } from '../utils/string'
5
- import type { MemoryRerankOutput } from './memory.service'
1
+ import { MEMORY } from '../../config/constants'
2
+ import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../../config/search'
3
+ import type { MemorySearchResult } from '../../db/memory-types'
4
+ import { compactWhitespace, truncateText } from '../../utils/string'
5
+ import type { MemoryRerankOutput } from './memory-rerank'
6
6
 
7
7
  export function getCandidateLimit(limit: number): number {
8
8
  return Math.max(limit * VECTOR_SEARCH_OVERFETCH_MULTIPLIER, MEMORY.DEFAULT_CANDIDATE_LIMIT)