@lota-sdk/core 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,914 +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 type { MemoryRerankerStrategy } from '../runtime/runtime-config'
27
- import { getRuntimeConfig } from '../runtime/runtime-config'
28
- import { createMemoryRerankerAgent, MEMORY_RERANKER_PROMPT } from '../system-agents/memory-reranker.agent'
29
- import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../system-agents/memory.agent'
30
- import { clampImportance, compactWhitespace, truncateText } from '../utils/string'
31
- import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory-utils'
32
- import { rerankService } from './rerank.service'
33
-
34
- const ORG_MEMORY_TYPE = 'fact'
35
- const RERANK_CANDIDATE_MAX_CHARS = 500
36
- const MAX_MEMORY_RESULTS_PER_SCOPE = 10
37
- const PRESEEDED_MEMORY_LIMIT = 5
38
- const PRESEEDED_MEMORY_MAX_CHARS = 300
39
- const PRESEEDED_MIN_IMPORTANCE = 0.7
40
- const PRESEEDED_MEMORY_DURABILITY: MemoryRecord['durability'] = 'core'
41
- const MAX_CONVERSATION_HISTORY_MESSAGES = 24
42
- const MAX_CONVERSATION_MESSAGE_CHARS = 1_200
43
- const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
44
- const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
45
- const ONBOARDING_MEMORY_MAX_FACTS = 16
46
- const MAX_ORG_MEMORY_CLIENTS = 128
47
- const LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD = 0.45
48
- const ONBOARDING_MEMORY_EXTRACTION_PROMPT =
49
- '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.'
50
- const DIRECT_MEMORY_ASSESSMENT_PROMPT =
51
- '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.'
52
- const RERANK_SECTION_TITLE = 'Most relevant memories'
53
-
54
- const helperModelRuntime = createHelperModelRuntime()
55
-
56
- const MemoryRerankOutputSchema = z.object({
57
- sections: z.array(
58
- z.object({
59
- title: z.string(),
60
- items: z.array(
61
- z.object({
62
- id: z.string(),
63
- relevance: z.string().describe('Short relevance reason. Use empty string when no reason is needed.'),
64
- }),
65
- ),
66
- }),
67
- ),
68
- })
69
-
70
- export type MemoryRerankOutput = z.infer<typeof MemoryRerankOutputSchema>
71
-
72
- const isRoutableAgentName = (value?: string): value is string => Boolean(value && agentRoster.includes(value))
73
-
74
- class MemoryService {
75
- private orgMemoryCache = new Map<string, Memory>()
76
-
77
- private getOrCreateMemory(cacheKey: string, cache: Map<string, Memory>): Memory {
78
- const cached = cache.get(cacheKey)
79
- if (cached) {
80
- cache.delete(cacheKey)
81
- cache.set(cacheKey, cached)
82
- return cached
83
- }
84
-
85
- const memory = new Memory({ createAgent: createOrgMemoryAgent }, { customPrompt: ORG_MEMORY_PROMPT })
86
-
87
- if (cache.size >= MAX_ORG_MEMORY_CLIENTS) {
88
- const oldestKey = cache.keys().next().value
89
- if (typeof oldestKey === 'string') {
90
- cache.delete(oldestKey)
91
- aiLogger.debug`Evicted cached org memory client for ${oldestKey}`
92
- }
93
- }
94
-
95
- cache.set(cacheKey, memory)
96
- aiLogger.debug`Memory client created and cached for ${cacheKey}`
97
- return memory
98
- }
99
-
100
- private getOrgMemory(orgId: string): Memory {
101
- const cacheKey = scopeId(ORG_SCOPE_PREFIX, orgId)
102
- return this.getOrCreateMemory(cacheKey, this.orgMemoryCache)
103
- }
104
-
105
- private truncateCandidateText(value: string): string {
106
- return truncateText(compactWhitespace(value), RERANK_CANDIDATE_MAX_CHARS)
107
- }
108
-
109
- private readNumericMetadata(metadata: Record<string, unknown>, key: string): number {
110
- const value = metadata[key]
111
- if (typeof value === 'number' && Number.isFinite(value)) return value
112
- return 0
113
- }
114
-
115
- private buildRerankerCandidateText(candidate: MemorySearchResult): string {
116
- const metadata = candidate.metadata
117
- const relationCount = this.readNumericMetadata(metadata, 'relationCount')
118
- const supportCount = this.readNumericMetadata(metadata, 'supportCount')
119
- const contradictCount = this.readNumericMetadata(metadata, 'contradictCount')
120
-
121
- const relatedContextRaw = metadata.relatedContext
122
- const relatedContext = Array.isArray(relatedContextRaw)
123
- ? relatedContextRaw
124
- .slice(0, 3)
125
- .map((item) => String(item).trim())
126
- .filter((item) => item.length > 0)
127
- : []
128
-
129
- const contextLines: string[] = []
130
- if (relatedContext.length > 0) {
131
- contextLines.push(`Related context: ${relatedContext.join(' | ')}`)
132
- }
133
- if (relationCount > 0 || supportCount > 0 || contradictCount > 0) {
134
- contextLines.push(
135
- `Graph signals: relations=${relationCount}; supports=${supportCount}; contradicts=${contradictCount}`,
136
- )
137
- }
138
-
139
- if (contextLines.length === 0) return candidate.content
140
- return `${candidate.content}\n\n${contextLines.join('\n')}`
141
- }
142
-
143
- private normalizeConversationText(value: string, maxChars: number): string {
144
- const normalized = compactWhitespace(value)
145
- if (!normalized) return ''
146
- return truncateText(normalized, maxChars)
147
- }
148
-
149
- private resolveAgentScopeNames(agentName?: string, agentNames: string[] = []): string[] {
150
- const unique = new Set<string>()
151
- if (typeof agentName === 'string' && agentName.trim()) {
152
- unique.add(agentName.trim())
153
- }
154
- for (const candidate of agentNames) {
155
- if (typeof candidate !== 'string') continue
156
- const normalized = candidate.trim()
157
- if (!normalized) continue
158
- unique.add(normalized)
159
- }
160
- return [...unique].filter((name) => isRoutableAgentName(name))
161
- }
162
-
163
- private normalizePreSeededMemoryText(value: string): string {
164
- return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
165
- }
166
-
167
- private formatPreSeededMemoriesSection(memories: MemoryRecord[]): string | undefined {
168
- const lines = memories
169
- .map((memory) => this.normalizePreSeededMemoryText(memory.content))
170
- .filter((line) => line.length > 0)
171
- .map((line) => `- ${line}`)
172
- if (lines.length === 0) return undefined
173
-
174
- return ['<pre-seeded-memories>', ...lines, '</pre-seeded-memories>'].join('\n')
175
- }
176
-
177
- private buildConversationMessages(params: {
178
- input: string
179
- output: string
180
- historyMessages: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
181
- memoryBlock?: string
182
- attachmentContext?: string
183
- }): { messages: Message[]; normalizedInput: string; sanitizedOutput: string } {
184
- const normalizedInput = this.normalizeConversationText(params.input, MAX_CONVERSATION_MESSAGE_CHARS)
185
- const sanitizedOutput = this.normalizeConversationText(
186
- sanitizeAgentOutputForMemory(params.output),
187
- MAX_CONVERSATION_MESSAGE_CHARS,
188
- )
189
- if (!normalizedInput || !sanitizedOutput) {
190
- return { messages: [], normalizedInput, sanitizedOutput }
191
- }
192
-
193
- const messages: Message[] = []
194
-
195
- const normalizedHistory: Message[] = params.historyMessages
196
- .map((message): Message | null => {
197
- const role: Message['role'] = message.role === 'agent' ? 'agent' : 'user'
198
- const normalized = this.normalizeConversationText(
199
- role === 'agent' ? sanitizeAgentOutputForMemory(message.content) : message.content,
200
- MAX_CONVERSATION_MESSAGE_CHARS,
201
- )
202
- if (!normalized) return null
203
- return { role, content: normalized }
204
- })
205
- .filter((message): message is Message => message !== null)
206
- .slice(-MAX_CONVERSATION_HISTORY_MESSAGES)
207
-
208
- messages.push(...normalizedHistory)
209
-
210
- const normalizedMemoryBlock = this.normalizeConversationText(
211
- params.memoryBlock ?? '',
212
- MAX_CONVERSATION_MEMORY_BLOCK_CHARS,
213
- )
214
- if (normalizedMemoryBlock) {
215
- messages.push({ role: 'user', content: `Thread memory block:\n${normalizedMemoryBlock}` })
216
- }
217
-
218
- const normalizedAttachmentContext = this.normalizeConversationText(
219
- params.attachmentContext ?? '',
220
- MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS,
221
- )
222
- if (normalizedAttachmentContext) {
223
- messages.push({ role: 'user', content: `Attachment context:\n${normalizedAttachmentContext}` })
224
- }
225
-
226
- messages.push({ role: 'user', content: normalizedInput })
227
- messages.push({ role: 'agent', content: sanitizedOutput })
228
-
229
- return { messages, normalizedInput, sanitizedOutput }
230
- }
231
-
232
- private async rerankCandidates(
233
- query: string,
234
- candidates: MemorySearchResult[],
235
- maxItems: number,
236
- ): Promise<MemoryRerankOutput | null> {
237
- if (candidates.length === 0) return null
238
-
239
- try {
240
- return this.getRerankerStrategy() === 'rerank'
241
- ? await this.rerankCandidatesWithRerankService(query, candidates, maxItems)
242
- : await this.rerankCandidatesWithHelper(query, candidates, maxItems)
243
- } catch (error) {
244
- aiLogger.warn`Memory reranker failed: ${error}`
245
- return null
246
- }
247
- }
248
-
249
- private async rerankCandidatesMultiScope(
250
- query: string,
251
- scopedCandidates: Array<{ scopeTag: string; candidates: MemorySearchResult[] }>,
252
- maxItems: number,
253
- ): Promise<MemoryRerankOutput | null> {
254
- const flattened = scopedCandidates.flatMap(({ scopeTag, candidates }) =>
255
- candidates.map((candidate) => ({ ...candidate, scopeTag })),
256
- )
257
- if (flattened.length === 0 || flattened.length <= maxItems) return null
258
-
259
- try {
260
- return this.getRerankerStrategy() === 'rerank'
261
- ? await this.rerankCandidatesMultiScopeWithRerankService(query, flattened, maxItems)
262
- : await this.rerankCandidatesMultiScopeWithHelper(query, flattened, maxItems)
263
- } catch (error) {
264
- aiLogger.warn`Multi-scope memory reranker failed: ${error}`
265
- return null
266
- }
267
- }
268
-
269
- private getRerankerStrategy(): MemoryRerankerStrategy {
270
- return getRuntimeConfig().memory.rerankerStrategy
271
- }
272
-
273
- private buildRerankOutput(ids: string[], title = RERANK_SECTION_TITLE): MemoryRerankOutput {
274
- return { sections: [{ title, items: ids.map((id) => ({ id, relevance: '' })) }] }
275
- }
276
-
277
- private async rerankCandidatesWithHelper(
278
- query: string,
279
- candidates: MemorySearchResult[],
280
- maxItems: number,
281
- ): Promise<MemoryRerankOutput> {
282
- return await helperModelRuntime.generateHelperStructured({
283
- tag: 'memory-reranker',
284
- createAgent: createMemoryRerankerAgent,
285
- defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
286
- messages: [
287
- {
288
- role: 'user',
289
- content: JSON.stringify({
290
- query,
291
- maxItems,
292
- candidates: candidates.map((candidate) => ({
293
- id: candidate.id,
294
- text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
295
- score: candidate.score,
296
- })),
297
- }),
298
- },
299
- ],
300
- schema: MemoryRerankOutputSchema,
301
- })
302
- }
303
-
304
- private async rerankCandidatesWithRerankService(
305
- query: string,
306
- candidates: MemorySearchResult[],
307
- maxItems: number,
308
- ): Promise<MemoryRerankOutput> {
309
- const reranked = await rerankService.rerankDocuments({
310
- query,
311
- topN: maxItems,
312
- documents: candidates.map((candidate) => ({
313
- id: candidate.id,
314
- text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
315
- })),
316
- })
317
-
318
- return this.buildRerankOutput(reranked.results.map((item) => item.id))
319
- }
320
-
321
- private async rerankCandidatesMultiScopeWithHelper(
322
- query: string,
323
- flattenedCandidates: Array<MemorySearchResult & { scopeTag: string }>,
324
- maxItems: number,
325
- ): Promise<MemoryRerankOutput> {
326
- return await helperModelRuntime.generateHelperStructured({
327
- tag: 'memory-reranker-multi-scope',
328
- createAgent: createMemoryRerankerAgent,
329
- defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
330
- messages: [
331
- {
332
- role: 'user',
333
- content: JSON.stringify({
334
- query,
335
- maxItems,
336
- candidates: flattenedCandidates.map((candidate) => ({
337
- id: candidate.id,
338
- text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
339
- score: candidate.score,
340
- scope: candidate.scopeTag,
341
- })),
342
- }),
343
- },
344
- ],
345
- schema: MemoryRerankOutputSchema,
346
- })
347
- }
348
-
349
- private async rerankCandidatesMultiScopeWithRerankService(
350
- query: string,
351
- flattenedCandidates: Array<MemorySearchResult & { scopeTag: string }>,
352
- maxItems: number,
353
- ): Promise<MemoryRerankOutput> {
354
- const reranked = await rerankService.rerankDocuments({
355
- query,
356
- topN: maxItems,
357
- documents: flattenedCandidates.map((candidate) => ({
358
- id: candidate.id,
359
- text: this.truncateCandidateText(
360
- `${this.buildRerankerCandidateText(candidate)}\n\nScope: ${candidate.scopeTag}`,
361
- ),
362
- })),
363
- })
364
-
365
- return this.buildRerankOutput(
366
- reranked.results.map((item) => item.id),
367
- 'Top matches across memory scopes',
368
- )
369
- }
370
-
371
- private async searchMemories({
372
- query,
373
- memory,
374
- scopeId,
375
- memoryType,
376
- fastMode = true,
377
- }: {
378
- query: string
379
- memory: Memory
380
- scopeId: string
381
- memoryType: MemoryType
382
- fastMode?: boolean
383
- }): Promise<string> {
384
- const limit = getRuntimeConfig().memory.searchK
385
- const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
386
-
387
- const candidates = await memory.searchCandidates(query, {
388
- scopeId,
389
- limit: candidateLimit,
390
- memoryType,
391
- fastMode,
392
- includeNeighborContext: !fastMode,
393
- })
394
-
395
- aiLogger.debug`Memory search candidates (scopeId: ${scopeId}, candidates: ${candidates.length})`
396
-
397
- if (candidates.length === 0) {
398
- return 'No stored memories.'
399
- }
400
-
401
- if (fastMode || candidates.length <= limit) {
402
- aiLogger.debug`Skipping reranking (candidates: ${candidates.length} <= limit: ${limit})`
403
- return formatMemoryResults(candidates.slice(0, limit))
404
- }
405
-
406
- const reranked = await this.rerankCandidates(query, candidates, limit)
407
- return formatRerankedResults(reranked, candidates, limit)
408
- }
409
-
410
- async searchOrganizationMemories(orgId: string, query: string): Promise<string> {
411
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
412
- aiLogger.debug`Organization memory search requested (orgId: ${orgId}, scopeId: ${orgScopeId}, queryLength: ${query.length})`
413
- const memory = this.getOrgMemory(orgId)
414
-
415
- const results = await this.searchMemories({ query, memory, scopeId: orgScopeId, memoryType: ORG_MEMORY_TYPE })
416
- aiLogger.debug`Organization memory search completed (resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
417
- return results
418
- }
419
-
420
- async getStaleMemories(orgId: string): Promise<string> {
421
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
422
- const memory = this.getOrgMemory(orgId)
423
- try {
424
- const stale = await memory.getStaleMemories(orgScopeId)
425
- if (stale.length === 0) return ''
426
- const items = stale.map((m) => `- [NEEDS REVIEW] ${m.content}`).join('\n')
427
- return `Memories flagged for review (parent fact was superseded):\n${items}`
428
- } catch (error) {
429
- aiLogger.warn`Failed to get stale memories: ${error}`
430
- return ''
431
- }
432
- }
433
-
434
- async searchOrganizationMemoriesRaw(
435
- orgId: string,
436
- query: string,
437
- options?: { fastMode?: boolean; limit?: number },
438
- ): Promise<string> {
439
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
440
- aiLogger.debug`searchOrganizationMemoriesRaw - orgId: "${orgId}", scopeId: "${orgScopeId}"`
441
- const memory = this.getOrgMemory(orgId)
442
- const fastMode = options?.fastMode ?? true
443
- const searchK = getRuntimeConfig().memory.searchK
444
- const limit = options?.limit ?? (fastMode ? Math.min(searchK, 4) : searchK)
445
-
446
- const candidates = await memory.searchCandidates(query, {
447
- scopeId: orgScopeId,
448
- limit,
449
- memoryType: ORG_MEMORY_TYPE,
450
- fastMode,
451
- includeNeighborContext: !fastMode,
452
- })
453
- aiLogger.debug`Organization memory search (raw) completed (candidates: ${candidates.length})`
454
- return formatMemoryResults(candidates)
455
- }
456
-
457
- async searchAgentMemories(orgId: string, agentName: string, query: string): Promise<string> {
458
- if (!isRoutableAgentName(agentName)) {
459
- aiLogger.debug`Agent memory search skipped - invalid agentName: ${agentName}`
460
- return 'No stored memories.'
461
- }
462
-
463
- const scoped = agentScopeId(orgId, agentName)
464
- aiLogger.debug`Agent memory search requested (orgId: ${orgId}, agentName: ${agentName}, scopeId: ${scoped}, queryLength: ${query.length})`
465
- const memory = this.getOrgMemory(orgId)
466
-
467
- const results = await this.searchMemories({ query, memory, scopeId: scoped, memoryType: ORG_MEMORY_TYPE })
468
- aiLogger.debug`Agent memory search completed (agentName: ${agentName}, resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
469
- return results
470
- }
471
-
472
- async searchOrgMemoriesForAgent(orgId: string, agentName: string, query: string): Promise<string> {
473
- if (!isRoutableAgentName(agentName)) {
474
- return this.searchOrganizationMemories(orgId, query)
475
- }
476
-
477
- const [agentResult, orgResult] = await Promise.all([
478
- this.searchAgentMemories(orgId, agentName, query),
479
- this.searchOrganizationMemories(orgId, query),
480
- ])
481
-
482
- return `Agent memory (${agentName}):\n${agentResult}\n\nGlobal org memory:\n${orgResult}`
483
- }
484
-
485
- async listOrganizationMemoryRecords(params: {
486
- orgId: string
487
- limit?: number
488
- memoryType?: MemoryType
489
- metadataEquals?: Record<string, MemoryListScalar>
490
- metadataNotEquals?: Record<string, MemoryListScalar>
491
- sort?: 'createdAtAsc' | 'createdAtDesc'
492
- }): Promise<MemoryRecord[]> {
493
- const { orgId, ...listOptions } = params
494
- const orgMemory = this.getOrgMemory(orgId)
495
- return orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions })
496
- }
497
-
498
- async getTopMemories(params: { orgId: string; agentName?: string; limit?: number }): Promise<string | undefined> {
499
- const orgMemory = this.getOrgMemory(params.orgId)
500
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, params.orgId)
501
- const requestedLimit = params.limit ?? PRESEEDED_MEMORY_LIMIT
502
- const limit = Math.max(1, Math.min(requestedLimit, PRESEEDED_MEMORY_LIMIT))
503
-
504
- const orgRequest = orgMemory.listTopMemories({
505
- scopeId: orgScopeId,
506
- limit,
507
- memoryType: ORG_MEMORY_TYPE,
508
- durability: PRESEEDED_MEMORY_DURABILITY,
509
- minImportance: PRESEEDED_MIN_IMPORTANCE,
510
- })
511
-
512
- const agentRequest =
513
- params.agentName && isRoutableAgentName(params.agentName)
514
- ? orgMemory.listTopMemories({
515
- scopeId: agentScopeId(params.orgId, params.agentName),
516
- limit,
517
- memoryType: ORG_MEMORY_TYPE,
518
- durability: PRESEEDED_MEMORY_DURABILITY,
519
- minImportance: PRESEEDED_MIN_IMPORTANCE,
520
- })
521
- : Promise.resolve([] as MemoryRecord[])
522
-
523
- const [orgTopMemories, agentTopMemories] = await Promise.all([orgRequest, agentRequest])
524
- const combined = [...agentTopMemories, ...orgTopMemories].sort((left, right) => {
525
- if (right.importance !== left.importance) return right.importance - left.importance
526
- if (right.accessCount !== left.accessCount) return right.accessCount - left.accessCount
527
- const rightLastAccess = right.lastAccessedAt?.getTime() ?? 0
528
- const leftLastAccess = left.lastAccessedAt?.getTime() ?? 0
529
- if (rightLastAccess !== leftLastAccess) return rightLastAccess - leftLastAccess
530
- return right.createdAt.getTime() - left.createdAt.getTime()
531
- })
532
-
533
- const deduped: MemoryRecord[] = []
534
- const seen = new Set<string>()
535
- for (const memory of combined) {
536
- const normalizedKey = compactWhitespace(memory.content).toLowerCase()
537
- if (!normalizedKey || seen.has(normalizedKey)) continue
538
- seen.add(normalizedKey)
539
- deduped.push(memory)
540
- if (deduped.length >= limit) break
541
- }
542
-
543
- return this.formatPreSeededMemoriesSection(deduped)
544
- }
545
-
546
- async searchAllMemoriesBatched({
547
- orgId,
548
- agentName,
549
- query,
550
- fastMode = true,
551
- allowMultiScopeRerank = true,
552
- }: {
553
- orgId: string
554
- agentName?: string
555
- query: string
556
- fastMode?: boolean
557
- allowMultiScopeRerank?: boolean
558
- }): Promise<string> {
559
- const limit = Math.min(getRuntimeConfig().memory.searchK, MAX_MEMORY_RESULTS_PER_SCOPE)
560
- const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
561
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
562
- const orgMemory = this.getOrgMemory(orgId)
563
-
564
- const retrievalTasks = [
565
- {
566
- scopeTag: 'org',
567
- retrieve: async () =>
568
- await orgMemory.searchCandidates(query, {
569
- scopeId: orgScopeId,
570
- limit: candidateLimit,
571
- memoryType: ORG_MEMORY_TYPE,
572
- fastMode,
573
- includeNeighborContext: !fastMode,
574
- }),
575
- },
576
- ]
577
-
578
- if (isRoutableAgentName(agentName)) {
579
- const agentScoped = agentScopeId(orgId, agentName)
580
- retrievalTasks.push({
581
- scopeTag: `agent:${agentName}`,
582
- retrieve: async () =>
583
- await orgMemory.searchCandidates(query, {
584
- scopeId: agentScoped,
585
- limit: candidateLimit,
586
- memoryType: ORG_MEMORY_TYPE,
587
- fastMode,
588
- includeNeighborContext: !fastMode,
589
- }),
590
- })
591
- }
592
-
593
- const results = await executeScopedRetrieval<MemorySearchResult>(retrievalTasks)
594
-
595
- const totalCandidates = countScopedRetrievalCandidates(results)
596
- aiLogger.debug`Batched memory search candidates (scopes: ${results.length}, total: ${totalCandidates})`
597
-
598
- if (totalCandidates === 0) {
599
- return 'No stored memories.'
600
- }
601
-
602
- if (fastMode || !allowMultiScopeRerank) {
603
- const candidatesByScopeTag = scopedRetrievalToMap(results)
604
- return this.formatBatchedResults(null, candidatesByScopeTag, limit, agentName)
605
- }
606
-
607
- const reranked = await this.rerankCandidatesMultiScope(query, results, limit)
608
- const candidatesByScopeTag = scopedRetrievalToMap(results)
609
-
610
- return this.formatBatchedResults(reranked, candidatesByScopeTag, limit, agentName)
611
- }
612
-
613
- private formatBatchedResults(
614
- reranked: MemoryRerankOutput | null,
615
- candidatesByScopeTag: Map<string, MemorySearchResult[]>,
616
- limit: number,
617
- agentName?: string,
618
- ): string {
619
- if (reranked && reranked.sections.length > 0) {
620
- const allCandidates = Array.from(candidatesByScopeTag.values()).flat()
621
- return formatRerankedResults(reranked, allCandidates, limit)
622
- }
623
-
624
- const sections: string[] = []
625
-
626
- if (agentName) {
627
- const agentCandidates = candidatesByScopeTag.get(`agent:${agentName}`) ?? []
628
- if (agentCandidates.length > 0) {
629
- sections.push(`Agent memory (${agentName}):\n${formatMemoryResults(agentCandidates.slice(0, limit))}`)
630
- } else {
631
- sections.push(`Agent memory (${agentName}):\nNo stored memories.`)
632
- }
633
- }
634
-
635
- const orgCandidates = candidatesByScopeTag.get('org') ?? []
636
- if (orgCandidates.length > 0) {
637
- sections.push(
638
- `${agentName ? 'Global org memory' : 'Organization memory'}:\n${formatMemoryResults(orgCandidates.slice(0, limit))}`,
639
- )
640
- } else {
641
- sections.push(`${agentName ? 'Global org memory' : 'Organization memory'}:\nNo stored memories.`)
642
- }
643
-
644
- return sections.join('\n\n')
645
- }
646
-
647
- async createOrganizationMemory({
648
- orgId,
649
- content,
650
- memoryType,
651
- metadata,
652
- importance,
653
- durability,
654
- }: {
655
- orgId: string
656
- content: string
657
- memoryType: MemoryType
658
- metadata?: Record<string, unknown>
659
- importance?: number
660
- durability?: MemoryRecord['durability']
661
- }): Promise<string> {
662
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
663
- aiLogger.debug`createOrganizationMemory - orgId: "${orgId}", scopeId: "${orgScopeId}", content preview: "${content.slice(0, 50)}"`
664
- const memory = this.getOrgMemory(orgId)
665
- try {
666
- return await memory.insert(content, {
667
- scopeId: orgScopeId,
668
- memoryType,
669
- importance: importance ?? 1,
670
- durability,
671
- metadata: { orgId, ...metadata },
672
- })
673
- } catch (error) {
674
- if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
675
- aiLogger.debug`Organization memory already exists (hash conflict)`
676
- return ''
677
- }
678
- throw error
679
- }
680
- }
681
-
682
- async addOrganizationMemoryRelation({
683
- orgId,
684
- fromMemoryId,
685
- toMemoryId,
686
- relationType,
687
- confidence,
688
- }: {
689
- orgId: string
690
- fromMemoryId: string
691
- toMemoryId: string
692
- relationType: RelationType
693
- confidence?: number
694
- }): Promise<void> {
695
- const memory = this.getOrgMemory(orgId)
696
- await memory.addRelation(fromMemoryId, toMemoryId, relationType, confidence)
697
- }
698
-
699
- async createAgentMemory({
700
- orgId,
701
- agentName,
702
- content,
703
- memoryType,
704
- metadata,
705
- importance,
706
- }: {
707
- orgId: string
708
- agentName: string
709
- content: string
710
- memoryType: MemoryType
711
- metadata?: Record<string, unknown>
712
- importance?: number
713
- }): Promise<string> {
714
- if (!isRoutableAgentName(agentName)) {
715
- throw new Error(`Invalid agentName for agent memory: ${agentName as string}`)
716
- }
717
-
718
- const memory = this.getOrgMemory(orgId)
719
- const scoped = agentScopeId(orgId, agentName)
720
- try {
721
- return await memory.insert(content, {
722
- scopeId: scoped,
723
- memoryType,
724
- importance: importance ?? 1,
725
- metadata: { orgId, agentName, memoryScope: 'agent', ...metadata },
726
- })
727
- } catch (error) {
728
- if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
729
- aiLogger.debug`Agent memory already exists (hash conflict)`
730
- return ''
731
- }
732
- throw error
733
- }
734
- }
735
-
736
- async assessMemoryCandidate(params: {
737
- orgId: string
738
- content: string
739
- }): Promise<Pick<ExtractedFact, 'classification' | 'durability' | 'importance' | 'rationale'> | null> {
740
- const trimmed = compactWhitespace(params.content)
741
- if (!trimmed) return null
742
-
743
- const facts = await this.getOrgMemory(params.orgId).extractFactsFromMessages([{ role: 'user', content: trimmed }], {
744
- maxFacts: 1,
745
- customPrompt: DIRECT_MEMORY_ASSESSMENT_PROMPT,
746
- })
747
- if (facts.length === 0) return null
748
- const fact = facts[0]
749
-
750
- return {
751
- classification: fact.classification,
752
- durability: fact.durability,
753
- importance: clampImportance(fact.importance),
754
- rationale: fact.rationale,
755
- }
756
- }
757
-
758
- private shouldSkipExtractedFacts(facts: ExtractedFact[]): boolean {
759
- if (facts.length === 0) return true
760
- return facts.every(
761
- (fact) =>
762
- fact.durability === 'ephemeral' &&
763
- fact.importance <= LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD &&
764
- fact.classification !== 'durable',
765
- )
766
- }
767
-
768
- async updateOrganizationMemoryById({
769
- orgId,
770
- memoryId,
771
- content,
772
- }: {
773
- orgId: string
774
- memoryId: string
775
- content: string
776
- }): Promise<void> {
777
- const memory = this.getOrgMemory(orgId)
778
- await memory.updateMemory(memoryId, content)
779
- }
780
-
781
- async addExtractedFactsToScopes(params: {
782
- orgId: string
783
- facts: ExtractedFact[]
784
- source: string
785
- sourceMetadata?: Record<string, unknown>
786
- agentNames?: string[]
787
- acquireLock?: boolean
788
- }): Promise<void> {
789
- if (params.facts.length === 0) return
790
-
791
- const orgMemory = this.getOrgMemory(params.orgId)
792
- const scopes: AddOptions[] = [
793
- {
794
- scopeId: scopeId(ORG_SCOPE_PREFIX, params.orgId),
795
- memoryType: ORG_MEMORY_TYPE,
796
- metadata: { orgId: params.orgId, source: params.source, ...params.sourceMetadata },
797
- },
798
- ]
799
-
800
- for (const scopedAgentName of this.resolveAgentScopeNames(undefined, params.agentNames ?? [])) {
801
- scopes.push({
802
- scopeId: agentScopeId(params.orgId, scopedAgentName),
803
- memoryType: ORG_MEMORY_TYPE,
804
- metadata: {
805
- orgId: params.orgId,
806
- source: params.source,
807
- ...params.sourceMetadata,
808
- agentName: scopedAgentName,
809
- memoryScope: 'agent',
810
- },
811
- })
812
- }
813
-
814
- const preparedUpdates = await orgMemory.prepareFactsToScopes(params.facts, scopes)
815
- if (preparedUpdates.length === 0) return
816
-
817
- if (params.acquireLock === false) {
818
- await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
819
- } else {
820
- await withOrgMemoryLock(params.orgId, async () => {
821
- await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
822
- })
823
- }
824
- }
825
-
826
- async addConversationMemories({
827
- orgId,
828
- input,
829
- output,
830
- sourceId,
831
- source = 'chat',
832
- sourceMetadata,
833
- onboardStatus,
834
- agentName,
835
- historyMessages = [],
836
- memoryBlock,
837
- attachmentContext,
838
- agentNames = [],
839
- }: {
840
- orgId: string
841
- input: string
842
- output: string
843
- sourceId?: string
844
- source?: string
845
- sourceMetadata?: Record<string, unknown>
846
- onboardStatus?: string
847
- agentName?: string
848
- historyMessages?: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
849
- memoryBlock?: string
850
- attachmentContext?: string
851
- agentNames?: string[]
852
- }): Promise<void> {
853
- const { messages, normalizedInput, sanitizedOutput } = this.buildConversationMessages({
854
- input,
855
- output,
856
- historyMessages,
857
- memoryBlock,
858
- attachmentContext,
859
- })
860
-
861
- if (!normalizedInput || !sanitizedOutput || messages.length === 0) {
862
- aiLogger.debug`Skipping memory add - empty input or output`
863
- return
864
- }
865
-
866
- const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
867
- aiLogger.debug`addConversationMemories - orgId: "${orgId}", scopeId: "${orgScopeId}", sourceId: ${sourceId ?? 'none'}`
868
-
869
- const orgMemory = this.getOrgMemory(orgId)
870
-
871
- const scopes: AddOptions[] = [
872
- {
873
- scopeId: orgScopeId,
874
- memoryType: ORG_MEMORY_TYPE,
875
- metadata: { orgId, source, ...(sourceId ? { sourceId } : {}), ...sourceMetadata },
876
- },
877
- ]
878
-
879
- for (const scopedAgentName of this.resolveAgentScopeNames(agentName, agentNames)) {
880
- const agentId = agentScopeId(orgId, scopedAgentName)
881
- scopes.push({
882
- scopeId: agentId,
883
- memoryType: ORG_MEMORY_TYPE,
884
- metadata: {
885
- orgId,
886
- agentName: scopedAgentName,
887
- memoryScope: 'agent',
888
- source,
889
- ...(sourceId ? { sourceId } : {}),
890
- ...sourceMetadata,
891
- },
892
- })
893
- }
894
-
895
- const onboardingActive = onboardStatus !== undefined && onboardStatus !== 'completed'
896
- const extractionConfig = onboardingActive
897
- ? { maxFacts: ONBOARDING_MEMORY_MAX_FACTS, customPrompt: ONBOARDING_MEMORY_EXTRACTION_PROMPT }
898
- : undefined
899
- const extractedFacts = await orgMemory.extractFactsFromMessages(messages, extractionConfig)
900
- if (this.shouldSkipExtractedFacts(extractedFacts)) {
901
- aiLogger.debug`Skipping transient conversation memory`
902
- return
903
- }
904
- const preparedUpdates = await orgMemory.prepareFactsToScopes(extractedFacts, scopes)
905
- if (preparedUpdates.length === 0) return
906
-
907
- await withOrgMemoryLock(orgId, async () => {
908
- await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
909
- })
910
- aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
911
- }
912
- }
913
-
914
- export const memoryService = new MemoryService()