@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
@@ -1,844 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- import { agentRoster } from '../config/agent-defaults'
4
- import { aiLogger } from '../config/logger'
5
- import { Memory } from '../db/memory'
6
- import { isUniqueIndexConflict } from '../db/memory-store.helpers'
7
- import type {
8
- AddOptions,
9
- ExtractedFact,
10
- MemoryListScalar,
11
- MemoryRecord,
12
- MemorySearchResult,
13
- MemoryType,
14
- Message,
15
- RelationType,
16
- } from '../db/memory-types'
17
- import { withOrgMemoryLock } from '../redis/org-memory-lock'
18
- import { createHelperModelRuntime } from '../runtime/helper-model'
19
- import { sanitizeAgentOutputForMemory } from '../runtime/llm-content'
20
- import { ORG_SCOPE_PREFIX, agentScopeId, scopeId } from '../runtime/memory-scope'
21
- import {
22
- countScopedRetrievalCandidates,
23
- executeScopedRetrieval,
24
- scopedRetrievalToMap,
25
- } from '../runtime/retrieval-adapters'
26
- import { getRuntimeConfig } from '../runtime/runtime-config'
27
- import { createMemoryRerankerAgent, MEMORY_RERANKER_PROMPT } from '../system-agents/memory-reranker.agent'
28
- import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../system-agents/memory.agent'
29
- import { clampImportance, compactWhitespace, truncateText } from '../utils/string'
30
- import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory-utils'
31
-
32
- const ORG_MEMORY_TYPE = 'fact'
33
- const RERANK_CANDIDATE_MAX_CHARS = 500
34
- const MAX_MEMORY_RESULTS_PER_SCOPE = 10
35
- const PRESEEDED_MEMORY_LIMIT = 5
36
- const PRESEEDED_MEMORY_MAX_CHARS = 300
37
- const PRESEEDED_MIN_IMPORTANCE = 0.7
38
- const PRESEEDED_MEMORY_DURABILITY: MemoryRecord['durability'] = 'core'
39
- const MAX_CONVERSATION_HISTORY_MESSAGES = 24
40
- const MAX_CONVERSATION_MESSAGE_CHARS = 1_200
41
- const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
42
- const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
43
- const ONBOARDING_MEMORY_MAX_FACTS = 16
44
- const MAX_ORG_MEMORY_CLIENTS = 128
45
- const LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD = 0.45
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
- const helperModelRuntime = createHelperModelRuntime()
52
-
53
- const MemoryRerankOutputSchema = z.object({
54
- sections: z.array(
55
- z.object({
56
- title: z.string(),
57
- items: z.array(
58
- z.object({
59
- id: z.string(),
60
- relevance: z.string().describe('Short relevance reason. Use empty string when no reason is needed.'),
61
- }),
62
- ),
63
- }),
64
- ),
65
- })
66
-
67
- export type MemoryRerankOutput = z.infer<typeof MemoryRerankOutputSchema>
68
-
69
- const isRoutableAgentName = (value?: string): value is string => Boolean(value && agentRoster.includes(value))
70
-
71
- class MemoryService {
72
- private orgMemoryCache = new Map<string, Memory>()
73
-
74
- private getOrCreateMemory(cacheKey: string, cache: Map<string, Memory>): Memory {
75
- const cached = cache.get(cacheKey)
76
- if (cached) {
77
- cache.delete(cacheKey)
78
- cache.set(cacheKey, cached)
79
- return cached
80
- }
81
-
82
- const memory = new Memory({ createAgent: createOrgMemoryAgent }, { customPrompt: ORG_MEMORY_PROMPT })
83
-
84
- if (cache.size >= MAX_ORG_MEMORY_CLIENTS) {
85
- const oldestKey = cache.keys().next().value
86
- if (typeof oldestKey === 'string') {
87
- cache.delete(oldestKey)
88
- aiLogger.debug`Evicted cached org memory client for ${oldestKey}`
89
- }
90
- }
91
-
92
- cache.set(cacheKey, memory)
93
- aiLogger.debug`Memory client created and cached for ${cacheKey}`
94
- return memory
95
- }
96
-
97
- private getOrgMemory(orgId: string): Memory {
98
- const cacheKey = scopeId(ORG_SCOPE_PREFIX, orgId)
99
- return this.getOrCreateMemory(cacheKey, this.orgMemoryCache)
100
- }
101
-
102
- private truncateCandidateText(value: string): string {
103
- return truncateText(compactWhitespace(value), RERANK_CANDIDATE_MAX_CHARS)
104
- }
105
-
106
- private readNumericMetadata(metadata: Record<string, unknown>, key: string): number {
107
- const value = metadata[key]
108
- if (typeof value === 'number' && Number.isFinite(value)) return value
109
- return 0
110
- }
111
-
112
- private buildRerankerCandidateText(candidate: MemorySearchResult): string {
113
- const metadata = candidate.metadata
114
- const relationCount = this.readNumericMetadata(metadata, 'relationCount')
115
- const supportCount = this.readNumericMetadata(metadata, 'supportCount')
116
- const contradictCount = this.readNumericMetadata(metadata, 'contradictCount')
117
-
118
- const relatedContextRaw = metadata.relatedContext
119
- const relatedContext = Array.isArray(relatedContextRaw)
120
- ? relatedContextRaw
121
- .slice(0, 3)
122
- .map((item) => String(item).trim())
123
- .filter((item) => item.length > 0)
124
- : []
125
-
126
- const contextLines: string[] = []
127
- if (relatedContext.length > 0) {
128
- contextLines.push(`Related context: ${relatedContext.join(' | ')}`)
129
- }
130
- if (relationCount > 0 || supportCount > 0 || contradictCount > 0) {
131
- contextLines.push(
132
- `Graph signals: relations=${relationCount}; supports=${supportCount}; contradicts=${contradictCount}`,
133
- )
134
- }
135
-
136
- if (contextLines.length === 0) return candidate.content
137
- return `${candidate.content}\n\n${contextLines.join('\n')}`
138
- }
139
-
140
- private normalizeConversationText(value: string, maxChars: number): string {
141
- const normalized = compactWhitespace(value)
142
- if (!normalized) return ''
143
- return truncateText(normalized, maxChars)
144
- }
145
-
146
- private resolveAgentScopeNames(agentName?: string, agentNames: string[] = []): string[] {
147
- const unique = new Set<string>()
148
- if (typeof agentName === 'string' && agentName.trim()) {
149
- unique.add(agentName.trim())
150
- }
151
- for (const candidate of agentNames) {
152
- if (typeof candidate !== 'string') continue
153
- const normalized = candidate.trim()
154
- if (!normalized) continue
155
- unique.add(normalized)
156
- }
157
- return [...unique].filter((name) => isRoutableAgentName(name))
158
- }
159
-
160
- private normalizePreSeededMemoryText(value: string): string {
161
- return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
162
- }
163
-
164
- private formatPreSeededMemoriesSection(memories: MemoryRecord[]): string | undefined {
165
- const lines = memories
166
- .map((memory) => this.normalizePreSeededMemoryText(memory.content))
167
- .filter((line) => line.length > 0)
168
- .map((line) => `- ${line}`)
169
- if (lines.length === 0) return undefined
170
-
171
- return ['<pre-seeded-memories>', ...lines, '</pre-seeded-memories>'].join('\n')
172
- }
173
-
174
- private buildConversationMessages(params: {
175
- input: string
176
- output: string
177
- historyMessages: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
178
- memoryBlock?: string
179
- attachmentContext?: string
180
- }): { messages: Message[]; normalizedInput: string; sanitizedOutput: string } {
181
- const normalizedInput = this.normalizeConversationText(params.input, MAX_CONVERSATION_MESSAGE_CHARS)
182
- const sanitizedOutput = this.normalizeConversationText(
183
- sanitizeAgentOutputForMemory(params.output),
184
- MAX_CONVERSATION_MESSAGE_CHARS,
185
- )
186
- if (!normalizedInput || !sanitizedOutput) {
187
- return { messages: [], normalizedInput, sanitizedOutput }
188
- }
189
-
190
- const messages: Message[] = []
191
-
192
- const normalizedHistory: Message[] = params.historyMessages
193
- .map((message): Message | null => {
194
- const role: Message['role'] = message.role === 'agent' ? 'agent' : 'user'
195
- const normalized = this.normalizeConversationText(
196
- role === 'agent' ? sanitizeAgentOutputForMemory(message.content) : message.content,
197
- MAX_CONVERSATION_MESSAGE_CHARS,
198
- )
199
- if (!normalized) return null
200
- return { role, content: normalized }
201
- })
202
- .filter((message): message is Message => message !== null)
203
- .slice(-MAX_CONVERSATION_HISTORY_MESSAGES)
204
-
205
- messages.push(...normalizedHistory)
206
-
207
- const normalizedMemoryBlock = this.normalizeConversationText(
208
- params.memoryBlock ?? '',
209
- MAX_CONVERSATION_MEMORY_BLOCK_CHARS,
210
- )
211
- if (normalizedMemoryBlock) {
212
- messages.push({ role: 'user', content: `Thread memory block:\n${normalizedMemoryBlock}` })
213
- }
214
-
215
- const normalizedAttachmentContext = this.normalizeConversationText(
216
- params.attachmentContext ?? '',
217
- MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS,
218
- )
219
- if (normalizedAttachmentContext) {
220
- messages.push({ role: 'user', content: `Attachment context:\n${normalizedAttachmentContext}` })
221
- }
222
-
223
- messages.push({ role: 'user', content: normalizedInput })
224
- messages.push({ role: 'agent', content: sanitizedOutput })
225
-
226
- return { messages, normalizedInput, sanitizedOutput }
227
- }
228
-
229
- private async rerankCandidates(
230
- query: string,
231
- candidates: MemorySearchResult[],
232
- maxItems: number,
233
- ): Promise<MemoryRerankOutput | null> {
234
- if (candidates.length === 0) return null
235
-
236
- try {
237
- return await helperModelRuntime.generateHelperStructured({
238
- tag: 'memory-reranker',
239
- createAgent: createMemoryRerankerAgent,
240
- defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
241
- messages: [
242
- {
243
- role: 'user',
244
- content: JSON.stringify({
245
- query,
246
- maxItems,
247
- candidates: candidates.map((candidate) => ({
248
- id: candidate.id,
249
- text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
250
- score: candidate.score,
251
- })),
252
- }),
253
- },
254
- ],
255
- schema: MemoryRerankOutputSchema,
256
- })
257
- } catch (error) {
258
- aiLogger.warn`Memory reranker failed: ${error}`
259
- return null
260
- }
261
- }
262
-
263
- private async rerankCandidatesMultiScope(
264
- query: string,
265
- scopedCandidates: Array<{ scopeTag: string; candidates: MemorySearchResult[] }>,
266
- maxItems: number,
267
- ): Promise<MemoryRerankOutput | null> {
268
- const flattened = scopedCandidates.flatMap(({ scopeTag, candidates }) =>
269
- candidates.map((candidate) => ({ ...candidate, scopeTag })),
270
- )
271
- if (flattened.length === 0 || flattened.length <= maxItems) return null
272
-
273
- try {
274
- return await helperModelRuntime.generateHelperStructured({
275
- tag: 'memory-reranker-multi-scope',
276
- createAgent: createMemoryRerankerAgent,
277
- defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
278
- messages: [
279
- {
280
- role: 'user',
281
- content: JSON.stringify({
282
- query,
283
- maxItems,
284
- candidates: flattened.map((candidate) => ({
285
- id: candidate.id,
286
- text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
287
- score: candidate.score,
288
- scope: candidate.scopeTag,
289
- })),
290
- }),
291
- },
292
- ],
293
- schema: MemoryRerankOutputSchema,
294
- })
295
- } catch (error) {
296
- aiLogger.warn`Multi-scope memory reranker failed: ${error}`
297
- return null
298
- }
299
- }
300
-
301
- private async searchMemories({
302
- query,
303
- memory,
304
- scopeId,
305
- memoryType,
306
- fastMode = true,
307
- }: {
308
- query: string
309
- memory: Memory
310
- scopeId: string
311
- memoryType: MemoryType
312
- fastMode?: boolean
313
- }): Promise<string> {
314
- const limit = getRuntimeConfig().memory.searchK
315
- const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
316
-
317
- const candidates = await memory.searchCandidates(query, {
318
- scopeId,
319
- limit: candidateLimit,
320
- memoryType,
321
- fastMode,
322
- includeNeighborContext: !fastMode,
323
- })
324
-
325
- aiLogger.debug`Memory search candidates (scopeId: ${scopeId}, candidates: ${candidates.length})`
326
-
327
- if (candidates.length === 0) {
328
- return 'No stored memories.'
329
- }
330
-
331
- if (fastMode || candidates.length <= limit) {
332
- aiLogger.debug`Skipping reranking (candidates: ${candidates.length} <= limit: ${limit})`
333
- return formatMemoryResults(candidates.slice(0, limit))
334
- }
335
-
336
- const reranked = await this.rerankCandidates(query, candidates, limit)
337
- return formatRerankedResults(reranked, candidates, limit)
338
- }
339
-
340
- async searchOrganizationMemories(orgId: string, query: string): Promise<string> {
341
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
342
- aiLogger.debug`Organization memory search requested (orgId: ${orgId}, scopeId: ${orgScopeId}, queryLength: ${query.length})`
343
- const memory = this.getOrgMemory(orgId)
344
-
345
- const results = await this.searchMemories({ query, memory, scopeId: orgScopeId, memoryType: ORG_MEMORY_TYPE })
346
- aiLogger.debug`Organization memory search completed (resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
347
- return results
348
- }
349
-
350
- async getStaleMemories(orgId: string): Promise<string> {
351
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
352
- const memory = this.getOrgMemory(orgId)
353
- try {
354
- const stale = await memory.getStaleMemories(orgScopeId)
355
- if (stale.length === 0) return ''
356
- const items = stale.map((m) => `- [NEEDS REVIEW] ${m.content}`).join('\n')
357
- return `Memories flagged for review (parent fact was superseded):\n${items}`
358
- } catch (error) {
359
- aiLogger.warn`Failed to get stale memories: ${error}`
360
- return ''
361
- }
362
- }
363
-
364
- async searchOrganizationMemoriesRaw(
365
- orgId: string,
366
- query: string,
367
- options?: { fastMode?: boolean; limit?: number },
368
- ): Promise<string> {
369
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
370
- aiLogger.debug`searchOrganizationMemoriesRaw - orgId: "${orgId}", scopeId: "${orgScopeId}"`
371
- const memory = this.getOrgMemory(orgId)
372
- const fastMode = options?.fastMode ?? true
373
- const searchK = getRuntimeConfig().memory.searchK
374
- const limit = options?.limit ?? (fastMode ? Math.min(searchK, 4) : searchK)
375
-
376
- const candidates = await memory.searchCandidates(query, {
377
- scopeId: orgScopeId,
378
- limit,
379
- memoryType: ORG_MEMORY_TYPE,
380
- fastMode,
381
- includeNeighborContext: !fastMode,
382
- })
383
- aiLogger.debug`Organization memory search (raw) completed (candidates: ${candidates.length})`
384
- return formatMemoryResults(candidates)
385
- }
386
-
387
- async searchAgentMemories(orgId: string, agentName: string, query: string): Promise<string> {
388
- if (!isRoutableAgentName(agentName)) {
389
- aiLogger.debug`Agent memory search skipped - invalid agentName: ${agentName}`
390
- return 'No stored memories.'
391
- }
392
-
393
- const scoped = agentScopeId(orgId, agentName)
394
- aiLogger.debug`Agent memory search requested (orgId: ${orgId}, agentName: ${agentName}, scopeId: ${scoped}, queryLength: ${query.length})`
395
- const memory = this.getOrgMemory(orgId)
396
-
397
- const results = await this.searchMemories({ query, memory, scopeId: scoped, memoryType: ORG_MEMORY_TYPE })
398
- aiLogger.debug`Agent memory search completed (agentName: ${agentName}, resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
399
- return results
400
- }
401
-
402
- async searchOrgMemoriesForAgent(orgId: string, agentName: string, query: string): Promise<string> {
403
- if (!isRoutableAgentName(agentName)) {
404
- return this.searchOrganizationMemories(orgId, query)
405
- }
406
-
407
- const [agentResult, orgResult] = await Promise.all([
408
- this.searchAgentMemories(orgId, agentName, query),
409
- this.searchOrganizationMemories(orgId, query),
410
- ])
411
-
412
- return `Agent memory (${agentName}):\n${agentResult}\n\nGlobal org memory:\n${orgResult}`
413
- }
414
-
415
- async listOrganizationMemoryRecords(params: {
416
- orgId: string
417
- limit?: number
418
- memoryType?: MemoryType
419
- metadataEquals?: Record<string, MemoryListScalar>
420
- metadataNotEquals?: Record<string, MemoryListScalar>
421
- sort?: 'createdAtAsc' | 'createdAtDesc'
422
- }): Promise<MemoryRecord[]> {
423
- const { orgId, ...listOptions } = params
424
- const orgMemory = this.getOrgMemory(orgId)
425
- return orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions })
426
- }
427
-
428
- async getTopMemories(params: { orgId: string; agentName?: string; limit?: number }): Promise<string | undefined> {
429
- const orgMemory = this.getOrgMemory(params.orgId)
430
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, params.orgId)
431
- const requestedLimit = params.limit ?? PRESEEDED_MEMORY_LIMIT
432
- const limit = Math.max(1, Math.min(requestedLimit, PRESEEDED_MEMORY_LIMIT))
433
-
434
- const orgRequest = orgMemory.listTopMemories({
435
- scopeId: orgScopeId,
436
- limit,
437
- memoryType: ORG_MEMORY_TYPE,
438
- durability: PRESEEDED_MEMORY_DURABILITY,
439
- minImportance: PRESEEDED_MIN_IMPORTANCE,
440
- })
441
-
442
- const agentRequest =
443
- params.agentName && isRoutableAgentName(params.agentName)
444
- ? orgMemory.listTopMemories({
445
- scopeId: agentScopeId(params.orgId, params.agentName),
446
- limit,
447
- memoryType: ORG_MEMORY_TYPE,
448
- durability: PRESEEDED_MEMORY_DURABILITY,
449
- minImportance: PRESEEDED_MIN_IMPORTANCE,
450
- })
451
- : Promise.resolve([] as MemoryRecord[])
452
-
453
- const [orgTopMemories, agentTopMemories] = await Promise.all([orgRequest, agentRequest])
454
- const combined = [...agentTopMemories, ...orgTopMemories].sort((left, right) => {
455
- if (right.importance !== left.importance) return right.importance - left.importance
456
- if (right.accessCount !== left.accessCount) return right.accessCount - left.accessCount
457
- const rightLastAccess = right.lastAccessedAt?.getTime() ?? 0
458
- const leftLastAccess = left.lastAccessedAt?.getTime() ?? 0
459
- if (rightLastAccess !== leftLastAccess) return rightLastAccess - leftLastAccess
460
- return right.createdAt.getTime() - left.createdAt.getTime()
461
- })
462
-
463
- const deduped: MemoryRecord[] = []
464
- const seen = new Set<string>()
465
- for (const memory of combined) {
466
- const normalizedKey = compactWhitespace(memory.content).toLowerCase()
467
- if (!normalizedKey || seen.has(normalizedKey)) continue
468
- seen.add(normalizedKey)
469
- deduped.push(memory)
470
- if (deduped.length >= limit) break
471
- }
472
-
473
- return this.formatPreSeededMemoriesSection(deduped)
474
- }
475
-
476
- async searchAllMemoriesBatched({
477
- orgId,
478
- agentName,
479
- query,
480
- fastMode = true,
481
- allowMultiScopeRerank = true,
482
- }: {
483
- orgId: string
484
- agentName?: string
485
- query: string
486
- fastMode?: boolean
487
- allowMultiScopeRerank?: boolean
488
- }): Promise<string> {
489
- const limit = Math.min(getRuntimeConfig().memory.searchK, MAX_MEMORY_RESULTS_PER_SCOPE)
490
- const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
491
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
492
- const orgMemory = this.getOrgMemory(orgId)
493
-
494
- const retrievalTasks = [
495
- {
496
- scopeTag: 'org',
497
- retrieve: async () =>
498
- await orgMemory.searchCandidates(query, {
499
- scopeId: orgScopeId,
500
- limit: candidateLimit,
501
- memoryType: ORG_MEMORY_TYPE,
502
- fastMode,
503
- includeNeighborContext: !fastMode,
504
- }),
505
- },
506
- ]
507
-
508
- if (isRoutableAgentName(agentName)) {
509
- const agentScoped = agentScopeId(orgId, agentName)
510
- retrievalTasks.push({
511
- scopeTag: `agent:${agentName}`,
512
- retrieve: async () =>
513
- await orgMemory.searchCandidates(query, {
514
- scopeId: agentScoped,
515
- limit: candidateLimit,
516
- memoryType: ORG_MEMORY_TYPE,
517
- fastMode,
518
- includeNeighborContext: !fastMode,
519
- }),
520
- })
521
- }
522
-
523
- const results = await executeScopedRetrieval<MemorySearchResult>(retrievalTasks)
524
-
525
- const totalCandidates = countScopedRetrievalCandidates(results)
526
- aiLogger.debug`Batched memory search candidates (scopes: ${results.length}, total: ${totalCandidates})`
527
-
528
- if (totalCandidates === 0) {
529
- return 'No stored memories.'
530
- }
531
-
532
- if (fastMode || !allowMultiScopeRerank) {
533
- const candidatesByScopeTag = scopedRetrievalToMap(results)
534
- return this.formatBatchedResults(null, candidatesByScopeTag, limit, agentName)
535
- }
536
-
537
- const reranked = await this.rerankCandidatesMultiScope(query, results, limit)
538
- const candidatesByScopeTag = scopedRetrievalToMap(results)
539
-
540
- return this.formatBatchedResults(reranked, candidatesByScopeTag, limit, agentName)
541
- }
542
-
543
- private formatBatchedResults(
544
- reranked: MemoryRerankOutput | null,
545
- candidatesByScopeTag: Map<string, MemorySearchResult[]>,
546
- limit: number,
547
- agentName?: string,
548
- ): string {
549
- if (reranked && reranked.sections.length > 0) {
550
- const allCandidates = Array.from(candidatesByScopeTag.values()).flat()
551
- return formatRerankedResults(reranked, allCandidates, limit)
552
- }
553
-
554
- const sections: string[] = []
555
-
556
- if (agentName) {
557
- const agentCandidates = candidatesByScopeTag.get(`agent:${agentName}`) ?? []
558
- if (agentCandidates.length > 0) {
559
- sections.push(`Agent memory (${agentName}):\n${formatMemoryResults(agentCandidates.slice(0, limit))}`)
560
- } else {
561
- sections.push(`Agent memory (${agentName}):\nNo stored memories.`)
562
- }
563
- }
564
-
565
- const orgCandidates = candidatesByScopeTag.get('org') ?? []
566
- if (orgCandidates.length > 0) {
567
- sections.push(
568
- `${agentName ? 'Global org memory' : 'Organization memory'}:\n${formatMemoryResults(orgCandidates.slice(0, limit))}`,
569
- )
570
- } else {
571
- sections.push(`${agentName ? 'Global org memory' : 'Organization memory'}:\nNo stored memories.`)
572
- }
573
-
574
- return sections.join('\n\n')
575
- }
576
-
577
- async createOrganizationMemory({
578
- orgId,
579
- content,
580
- memoryType,
581
- metadata,
582
- importance,
583
- durability,
584
- }: {
585
- orgId: string
586
- content: string
587
- memoryType: MemoryType
588
- metadata?: Record<string, unknown>
589
- importance?: number
590
- durability?: MemoryRecord['durability']
591
- }): Promise<string> {
592
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
593
- aiLogger.debug`createOrganizationMemory - orgId: "${orgId}", scopeId: "${orgScopeId}", content preview: "${content.slice(0, 50)}"`
594
- const memory = this.getOrgMemory(orgId)
595
- try {
596
- return await memory.insert(content, {
597
- scopeId: orgScopeId,
598
- memoryType,
599
- importance: importance ?? 1,
600
- durability,
601
- metadata: { orgId, ...metadata },
602
- })
603
- } catch (error) {
604
- if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
605
- aiLogger.debug`Organization memory already exists (hash conflict)`
606
- return ''
607
- }
608
- throw error
609
- }
610
- }
611
-
612
- async addOrganizationMemoryRelation({
613
- orgId,
614
- fromMemoryId,
615
- toMemoryId,
616
- relationType,
617
- confidence,
618
- }: {
619
- orgId: string
620
- fromMemoryId: string
621
- toMemoryId: string
622
- relationType: RelationType
623
- confidence?: number
624
- }): Promise<void> {
625
- const memory = this.getOrgMemory(orgId)
626
- await memory.addRelation(fromMemoryId, toMemoryId, relationType, confidence)
627
- }
628
-
629
- async createAgentMemory({
630
- orgId,
631
- agentName,
632
- content,
633
- memoryType,
634
- metadata,
635
- importance,
636
- }: {
637
- orgId: string
638
- agentName: string
639
- content: string
640
- memoryType: MemoryType
641
- metadata?: Record<string, unknown>
642
- importance?: number
643
- }): Promise<string> {
644
- if (!isRoutableAgentName(agentName)) {
645
- throw new Error(`Invalid agentName for agent memory: ${agentName as string}`)
646
- }
647
-
648
- const memory = this.getOrgMemory(orgId)
649
- const scoped = agentScopeId(orgId, agentName)
650
- try {
651
- return await memory.insert(content, {
652
- scopeId: scoped,
653
- memoryType,
654
- importance: importance ?? 1,
655
- metadata: { orgId, agentName, memoryScope: 'agent', ...metadata },
656
- })
657
- } catch (error) {
658
- if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
659
- aiLogger.debug`Agent memory already exists (hash conflict)`
660
- return ''
661
- }
662
- throw error
663
- }
664
- }
665
-
666
- async assessMemoryCandidate(params: {
667
- orgId: string
668
- content: string
669
- }): Promise<Pick<ExtractedFact, 'classification' | 'durability' | 'importance' | 'rationale'> | null> {
670
- const trimmed = compactWhitespace(params.content)
671
- if (!trimmed) return null
672
-
673
- const facts = await this.getOrgMemory(params.orgId).extractFactsFromMessages([{ role: 'user', content: trimmed }], {
674
- maxFacts: 1,
675
- customPrompt: DIRECT_MEMORY_ASSESSMENT_PROMPT,
676
- })
677
- if (facts.length === 0) return null
678
- const fact = facts[0]
679
-
680
- return {
681
- classification: fact.classification,
682
- durability: fact.durability,
683
- importance: clampImportance(fact.importance),
684
- rationale: fact.rationale,
685
- }
686
- }
687
-
688
- private shouldSkipExtractedFacts(facts: ExtractedFact[]): boolean {
689
- if (facts.length === 0) return true
690
- return facts.every(
691
- (fact) =>
692
- fact.durability === 'ephemeral' &&
693
- fact.importance <= LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD &&
694
- fact.classification !== 'durable',
695
- )
696
- }
697
-
698
- async updateOrganizationMemoryById({
699
- orgId,
700
- memoryId,
701
- content,
702
- }: {
703
- orgId: string
704
- memoryId: string
705
- content: string
706
- }): Promise<void> {
707
- const memory = this.getOrgMemory(orgId)
708
- await memory.updateMemory(memoryId, content)
709
- }
710
-
711
- async addExtractedFactsToScopes(params: {
712
- orgId: string
713
- facts: ExtractedFact[]
714
- source: string
715
- sourceMetadata?: Record<string, unknown>
716
- agentNames?: string[]
717
- acquireLock?: boolean
718
- }): Promise<void> {
719
- if (params.facts.length === 0) return
720
-
721
- const orgMemory = this.getOrgMemory(params.orgId)
722
- const scopes: AddOptions[] = [
723
- {
724
- scopeId: scopeId(ORG_SCOPE_PREFIX, params.orgId),
725
- memoryType: ORG_MEMORY_TYPE,
726
- metadata: { orgId: params.orgId, source: params.source, ...params.sourceMetadata },
727
- },
728
- ]
729
-
730
- for (const scopedAgentName of this.resolveAgentScopeNames(undefined, params.agentNames ?? [])) {
731
- scopes.push({
732
- scopeId: agentScopeId(params.orgId, scopedAgentName),
733
- memoryType: ORG_MEMORY_TYPE,
734
- metadata: {
735
- orgId: params.orgId,
736
- source: params.source,
737
- ...params.sourceMetadata,
738
- agentName: scopedAgentName,
739
- memoryScope: 'agent',
740
- },
741
- })
742
- }
743
-
744
- const preparedUpdates = await orgMemory.prepareFactsToScopes(params.facts, scopes)
745
- if (preparedUpdates.length === 0) return
746
-
747
- if (params.acquireLock === false) {
748
- await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
749
- } else {
750
- await withOrgMemoryLock(params.orgId, async () => {
751
- await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
752
- })
753
- }
754
- }
755
-
756
- async addConversationMemories({
757
- orgId,
758
- input,
759
- output,
760
- sourceId,
761
- source = 'chat',
762
- sourceMetadata,
763
- onboardStatus,
764
- agentName,
765
- historyMessages = [],
766
- memoryBlock,
767
- attachmentContext,
768
- agentNames = [],
769
- }: {
770
- orgId: string
771
- input: string
772
- output: string
773
- sourceId?: string
774
- source?: string
775
- sourceMetadata?: Record<string, unknown>
776
- onboardStatus?: string
777
- agentName?: string
778
- historyMessages?: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
779
- memoryBlock?: string
780
- attachmentContext?: string
781
- agentNames?: string[]
782
- }): Promise<void> {
783
- const { messages, normalizedInput, sanitizedOutput } = this.buildConversationMessages({
784
- input,
785
- output,
786
- historyMessages,
787
- memoryBlock,
788
- attachmentContext,
789
- })
790
-
791
- if (!normalizedInput || !sanitizedOutput || messages.length === 0) {
792
- aiLogger.debug`Skipping memory add - empty input or output`
793
- return
794
- }
795
-
796
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
797
- aiLogger.debug`addConversationMemories - orgId: "${orgId}", scopeId: "${orgScopeId}", sourceId: ${sourceId ?? 'none'}`
798
-
799
- const orgMemory = this.getOrgMemory(orgId)
800
-
801
- const scopes: AddOptions[] = [
802
- {
803
- scopeId: orgScopeId,
804
- memoryType: ORG_MEMORY_TYPE,
805
- metadata: { orgId, source, ...(sourceId ? { sourceId } : {}), ...sourceMetadata },
806
- },
807
- ]
808
-
809
- for (const scopedAgentName of this.resolveAgentScopeNames(agentName, agentNames)) {
810
- const agentId = agentScopeId(orgId, scopedAgentName)
811
- scopes.push({
812
- scopeId: agentId,
813
- memoryType: ORG_MEMORY_TYPE,
814
- metadata: {
815
- orgId,
816
- agentName: scopedAgentName,
817
- memoryScope: 'agent',
818
- source,
819
- ...(sourceId ? { sourceId } : {}),
820
- ...sourceMetadata,
821
- },
822
- })
823
- }
824
-
825
- const onboardingActive = onboardStatus !== undefined && onboardStatus !== 'completed'
826
- const extractionConfig = onboardingActive
827
- ? { maxFacts: ONBOARDING_MEMORY_MAX_FACTS, customPrompt: ONBOARDING_MEMORY_EXTRACTION_PROMPT }
828
- : undefined
829
- const extractedFacts = await orgMemory.extractFactsFromMessages(messages, extractionConfig)
830
- if (this.shouldSkipExtractedFacts(extractedFacts)) {
831
- aiLogger.debug`Skipping transient conversation memory`
832
- return
833
- }
834
- const preparedUpdates = await orgMemory.prepareFactsToScopes(extractedFacts, scopes)
835
- if (preparedUpdates.length === 0) return
836
-
837
- await withOrgMemoryLock(orgId, async () => {
838
- await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
839
- })
840
- aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
841
- }
842
- }
843
-
844
- export const memoryService = new MemoryService()