@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,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)