@lota-sdk/core 0.1.5

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 (153) hide show
  1. package/infrastructure/schema/00_workstream.surql +55 -0
  2. package/infrastructure/schema/01_memory.surql +47 -0
  3. package/infrastructure/schema/02_execution_plan.surql +62 -0
  4. package/infrastructure/schema/03_learned_skill.surql +32 -0
  5. package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
  6. package/package.json +128 -0
  7. package/src/ai/definitions.ts +308 -0
  8. package/src/bifrost/bifrost.ts +256 -0
  9. package/src/config/agent-defaults.ts +99 -0
  10. package/src/config/constants.ts +33 -0
  11. package/src/config/env-shapes.ts +122 -0
  12. package/src/config/logger.ts +29 -0
  13. package/src/config/model-constants.ts +31 -0
  14. package/src/config/search.ts +17 -0
  15. package/src/config/workstream-defaults.ts +68 -0
  16. package/src/db/base.service.ts +55 -0
  17. package/src/db/cursor-pagination.ts +73 -0
  18. package/src/db/memory-query-builder.ts +207 -0
  19. package/src/db/memory-store.helpers.ts +118 -0
  20. package/src/db/memory-store.rows.ts +29 -0
  21. package/src/db/memory-store.ts +974 -0
  22. package/src/db/memory-types.ts +193 -0
  23. package/src/db/memory.ts +505 -0
  24. package/src/db/record-id.ts +78 -0
  25. package/src/db/service.ts +932 -0
  26. package/src/db/startup.ts +152 -0
  27. package/src/db/tables.ts +20 -0
  28. package/src/document/org-document-chunking.ts +224 -0
  29. package/src/document/parsing.ts +40 -0
  30. package/src/embeddings/provider.ts +76 -0
  31. package/src/index.ts +302 -0
  32. package/src/queues/context-compaction.queue.ts +82 -0
  33. package/src/queues/document-processor.queue.ts +118 -0
  34. package/src/queues/memory-consolidation.queue.ts +65 -0
  35. package/src/queues/post-chat-memory.queue.ts +128 -0
  36. package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
  37. package/src/queues/regular-chat-memory-digest.config.ts +12 -0
  38. package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
  39. package/src/queues/skill-extraction.config.ts +9 -0
  40. package/src/queues/skill-extraction.queue.ts +62 -0
  41. package/src/redis/connection.ts +176 -0
  42. package/src/redis/index.ts +30 -0
  43. package/src/redis/org-memory-lock.ts +43 -0
  44. package/src/redis/redis-lease-lock.ts +158 -0
  45. package/src/runtime/agent-contract.ts +1 -0
  46. package/src/runtime/agent-prompt-context.ts +119 -0
  47. package/src/runtime/agent-runtime-policy.ts +192 -0
  48. package/src/runtime/agent-stream-helpers.ts +117 -0
  49. package/src/runtime/agent-types.ts +22 -0
  50. package/src/runtime/approval-continuation.ts +16 -0
  51. package/src/runtime/chat-attachments.ts +46 -0
  52. package/src/runtime/chat-message.ts +10 -0
  53. package/src/runtime/chat-request-routing.ts +21 -0
  54. package/src/runtime/chat-run-orchestration.ts +25 -0
  55. package/src/runtime/chat-run-registry.ts +20 -0
  56. package/src/runtime/chat-types.ts +18 -0
  57. package/src/runtime/context-compaction-constants.ts +11 -0
  58. package/src/runtime/context-compaction-runtime.ts +86 -0
  59. package/src/runtime/context-compaction.ts +909 -0
  60. package/src/runtime/execution-plan.ts +59 -0
  61. package/src/runtime/helper-model.ts +405 -0
  62. package/src/runtime/indexed-repositories-policy.ts +28 -0
  63. package/src/runtime/instruction-sections.ts +8 -0
  64. package/src/runtime/llm-content.ts +71 -0
  65. package/src/runtime/memory-block.ts +264 -0
  66. package/src/runtime/memory-digest-policy.ts +14 -0
  67. package/src/runtime/memory-format.ts +8 -0
  68. package/src/runtime/memory-pipeline.ts +570 -0
  69. package/src/runtime/memory-prompts-fact.ts +47 -0
  70. package/src/runtime/memory-prompts-parse.ts +3 -0
  71. package/src/runtime/memory-prompts-update.ts +37 -0
  72. package/src/runtime/memory-scope.ts +43 -0
  73. package/src/runtime/plugin-types.ts +10 -0
  74. package/src/runtime/retrieval-adapters.ts +25 -0
  75. package/src/runtime/retrieval-pipeline.ts +3 -0
  76. package/src/runtime/runtime-extensions.ts +154 -0
  77. package/src/runtime/skill-extraction-policy.ts +3 -0
  78. package/src/runtime/team-consultation-orchestrator.ts +245 -0
  79. package/src/runtime/team-consultation-prompts.ts +32 -0
  80. package/src/runtime/title-helpers.ts +12 -0
  81. package/src/runtime/turn-lifecycle.ts +28 -0
  82. package/src/runtime/workstream-chat-helpers.ts +187 -0
  83. package/src/runtime/workstream-routing-policy.ts +301 -0
  84. package/src/runtime/workstream-state.ts +261 -0
  85. package/src/services/attachment.service.ts +159 -0
  86. package/src/services/chat-attachments.service.ts +17 -0
  87. package/src/services/chat-run-registry.service.ts +3 -0
  88. package/src/services/context-compaction-runtime.ts +13 -0
  89. package/src/services/context-compaction.service.ts +115 -0
  90. package/src/services/document-chunk.service.ts +141 -0
  91. package/src/services/execution-plan.service.ts +890 -0
  92. package/src/services/learned-skill.service.ts +328 -0
  93. package/src/services/memory-assessment.service.ts +43 -0
  94. package/src/services/memory.service.ts +807 -0
  95. package/src/services/memory.utils.ts +84 -0
  96. package/src/services/mutating-approval.service.ts +110 -0
  97. package/src/services/recent-activity-title.service.ts +74 -0
  98. package/src/services/recent-activity.service.ts +397 -0
  99. package/src/services/workstream-change-tracker.service.ts +313 -0
  100. package/src/services/workstream-message.service.ts +283 -0
  101. package/src/services/workstream-title.service.ts +58 -0
  102. package/src/services/workstream-turn-preparation.ts +1340 -0
  103. package/src/services/workstream-turn.ts +37 -0
  104. package/src/services/workstream.service.ts +854 -0
  105. package/src/services/workstream.types.ts +118 -0
  106. package/src/storage/attachment-parser.ts +101 -0
  107. package/src/storage/attachment-storage.service.ts +391 -0
  108. package/src/storage/attachments.types.ts +11 -0
  109. package/src/storage/attachments.utils.ts +58 -0
  110. package/src/storage/generated-document-storage.service.ts +55 -0
  111. package/src/system-agents/agent-result.ts +27 -0
  112. package/src/system-agents/context-compacter.agent.ts +46 -0
  113. package/src/system-agents/delegated-agent-factory.ts +177 -0
  114. package/src/system-agents/helper-agent-options.ts +20 -0
  115. package/src/system-agents/memory-reranker.agent.ts +38 -0
  116. package/src/system-agents/memory.agent.ts +58 -0
  117. package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
  118. package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
  119. package/src/system-agents/researcher.agent.ts +34 -0
  120. package/src/system-agents/skill-extractor.agent.ts +88 -0
  121. package/src/system-agents/skill-manager.agent.ts +80 -0
  122. package/src/system-agents/title-generator.agent.ts +42 -0
  123. package/src/system-agents/workstream-tracker.agent.ts +58 -0
  124. package/src/tools/execution-plan.tool.ts +163 -0
  125. package/src/tools/fetch-webpage.tool.ts +132 -0
  126. package/src/tools/firecrawl-client.ts +12 -0
  127. package/src/tools/memory-block.tool.ts +55 -0
  128. package/src/tools/read-file-parts.tool.ts +80 -0
  129. package/src/tools/remember-memory.tool.ts +85 -0
  130. package/src/tools/research-topic.tool.ts +15 -0
  131. package/src/tools/search-tools.ts +55 -0
  132. package/src/tools/search-web.tool.ts +175 -0
  133. package/src/tools/team-think.tool.ts +125 -0
  134. package/src/tools/tool-contract.ts +21 -0
  135. package/src/tools/user-questions.tool.ts +18 -0
  136. package/src/utils/async.ts +50 -0
  137. package/src/utils/date-time.ts +34 -0
  138. package/src/utils/error.ts +10 -0
  139. package/src/utils/errors.ts +28 -0
  140. package/src/utils/hono-error-handler.ts +71 -0
  141. package/src/utils/string.ts +51 -0
  142. package/src/workers/bootstrap.ts +44 -0
  143. package/src/workers/memory-consolidation.worker.ts +318 -0
  144. package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
  145. package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
  146. package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
  147. package/src/workers/skill-extraction.runner.ts +331 -0
  148. package/src/workers/skill-extraction.worker.ts +22 -0
  149. package/src/workers/utils/repo-indexer-chunker.ts +331 -0
  150. package/src/workers/utils/repo-structure-extractor.ts +645 -0
  151. package/src/workers/utils/repomix-process-concurrency.ts +65 -0
  152. package/src/workers/utils/sandbox-error.ts +5 -0
  153. package/src/workers/worker-utils.ts +182 -0
package/src/index.ts ADDED
@@ -0,0 +1,302 @@
1
+ import type { CoreWorkstreamProfile } from './config/agent-defaults'
2
+ import type { LotaWorkstreamConfig } from './config/workstream-defaults'
3
+ import type { SurrealDBService } from './db/service'
4
+ import type { startContextCompactionWorker } from './queues/context-compaction.queue'
5
+ import type {
6
+ scheduleRecurringConsolidation,
7
+ startMemoryConsolidationWorker,
8
+ } from './queues/memory-consolidation.queue'
9
+ import type { startPostChatMemoryWorker } from './queues/post-chat-memory.queue'
10
+ import type { startRecentActivityTitleRefinementWorker } from './queues/recent-activity-title-refinement.queue'
11
+ import type { startRegularChatMemoryDigestWorker } from './queues/regular-chat-memory-digest.queue'
12
+ import type { startSkillExtractionWorker } from './queues/skill-extraction.queue'
13
+ import type { RedisConnectionManager } from './redis/connection'
14
+ import type { isApprovalContinuationRequest } from './runtime/approval-continuation'
15
+ import type { LotaPlugin } from './runtime/plugin-types'
16
+ import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime/runtime-extensions'
17
+ import type { attachmentService } from './services/attachment.service'
18
+ import type { executionPlanService } from './services/execution-plan.service'
19
+ import type { memoryService } from './services/memory.service'
20
+ import type { verifyMutatingApproval } from './services/mutating-approval.service'
21
+ import type { recentActivityTitleService } from './services/recent-activity-title.service'
22
+ import type { recentActivityService } from './services/recent-activity.service'
23
+ import type { workstreamMessageService } from './services/workstream-message.service'
24
+ import type { workstreamTitleService } from './services/workstream-title.service'
25
+ import type {
26
+ createWorkstreamApprovalContinuationStream,
27
+ createWorkstreamTurnStream,
28
+ runWorkstreamTurnInBackground,
29
+ } from './services/workstream-turn'
30
+ import type { workstreamService } from './services/workstream.service'
31
+ import type { generatedDocumentStorageService } from './storage/generated-document-storage.service'
32
+
33
+ export type LotaAgentFactoryRegistry = Record<string, (...args: unknown[]) => unknown>
34
+
35
+ interface LotaRuntimeBuiltInWorkers {
36
+ startContextCompactionWorker: typeof startContextCompactionWorker
37
+ startMemoryConsolidationWorker: typeof startMemoryConsolidationWorker
38
+ startPostChatMemoryWorker: typeof startPostChatMemoryWorker
39
+ startRecentActivityTitleRefinementWorker: typeof startRecentActivityTitleRefinementWorker
40
+ startRegularChatMemoryDigestWorker: typeof startRegularChatMemoryDigestWorker
41
+ startSkillExtractionWorker: typeof startSkillExtractionWorker
42
+ scheduleRecurringConsolidation: typeof scheduleRecurringConsolidation
43
+ }
44
+
45
+ export interface LotaRuntimeConfig {
46
+ database: { url: string; namespace: string; database: string; username: string; password: string }
47
+ redis: { url: string }
48
+ aiGateway: { url: string; key: string; admin?: string; pass?: string; embeddingModel?: string }
49
+ s3: {
50
+ endpoint: string
51
+ bucket: string
52
+ region?: string
53
+ accessKeyId: string
54
+ secretAccessKey: string
55
+ attachmentUrlExpiresIn?: number
56
+ }
57
+ firecrawl: { apiKey: string; apiBaseUrl?: string }
58
+ logging?: { level?: 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal' }
59
+ memory?: { searchK?: number }
60
+ workstreams?: LotaWorkstreamConfig
61
+
62
+ agents: {
63
+ roster: readonly string[]
64
+ displayNames: Record<string, string>
65
+ shortDisplayNames?: Record<string, string>
66
+ teamConsultParticipants: readonly string[]
67
+ getCoreWorkstreamProfile?: (coreType: string) => CoreWorkstreamProfile
68
+ createAgent?: LotaAgentFactoryRegistry
69
+ buildAgentTools?: (...args: unknown[]) => unknown
70
+ getAgentRuntimeConfig?: (...args: unknown[]) => unknown
71
+ }
72
+
73
+ toolProviders?: Record<string, unknown>
74
+ extraSchemaFiles?: Array<string | URL>
75
+ extraWorkers?: Record<string, unknown>
76
+ pluginRuntime?: Record<string, LotaPlugin>
77
+ runtimeAdapters?: LotaRuntimeAdapters
78
+ turnHooks?: LotaRuntimeTurnHooks
79
+ }
80
+
81
+ export interface LotaRuntime {
82
+ services: {
83
+ database: SurrealDBService
84
+ databaseService: SurrealDBService
85
+ redis: RedisConnectionManager
86
+ closeRedisConnection: () => Promise<void>
87
+ attachmentService: typeof attachmentService
88
+ generatedDocumentStorageService: typeof generatedDocumentStorageService
89
+ memoryService: typeof memoryService
90
+ verifyMutatingApproval: typeof verifyMutatingApproval
91
+ recentActivityService: typeof recentActivityService
92
+ recentActivityTitleService: typeof recentActivityTitleService
93
+ executionPlanService: typeof executionPlanService
94
+ workstreamMessageService: typeof workstreamMessageService
95
+ workstreamService: typeof workstreamService
96
+ workstreamTitleService: typeof workstreamTitleService
97
+ createWorkstreamApprovalContinuationStream: typeof createWorkstreamApprovalContinuationStream
98
+ createWorkstreamTurnStream: typeof createWorkstreamTurnStream
99
+ isApprovalContinuationRequest: typeof isApprovalContinuationRequest
100
+ runWorkstreamTurnInBackground: typeof runWorkstreamTurnInBackground
101
+ }
102
+ redis: {
103
+ manager: RedisConnectionManager
104
+ getConnection: () => ReturnType<RedisConnectionManager['getConnection']>
105
+ getConnectionForBullMQ: () => ReturnType<RedisConnectionManager['getConnectionForBullMQ']>
106
+ closeConnection: () => Promise<void>
107
+ }
108
+ workers: LotaRuntimeBuiltInWorkers & Record<string, unknown>
109
+ schemaFiles: Array<string | URL>
110
+ contributions: { envKeys: readonly string[]; schemaFiles: Array<string | URL> }
111
+ config: LotaRuntimeConfig
112
+ plugins: Record<string, LotaPlugin>
113
+ connectPluginDatabases(): Promise<void>
114
+ connect(): Promise<void>
115
+ disconnect(): Promise<void>
116
+ }
117
+
118
+ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<LotaRuntime> {
119
+ const { lotaSdkEnvKeys, setEnv } = await import('./config/env-shapes')
120
+ const { configureLogger } = await import('./config/logger')
121
+ const { SurrealDBService: SurrealDBServiceClass, setDatabaseService } = await import('./db/service')
122
+ const { createRedisConnectionManager } = await import('./redis/connection')
123
+ const { setRedisConnectionManager } = await import('./redis/index')
124
+ const { configureAgents, configureAgentFactory } = await import('./config/agent-defaults')
125
+ const { configureWorkstreams } = await import('./config/workstream-defaults')
126
+ const { configureRuntimeExtensions } = await import('./runtime/runtime-extensions')
127
+
128
+ setEnv({
129
+ AI_GATEWAY_URL: config.aiGateway.url,
130
+ AI_GATEWAY_KEY: config.aiGateway.key,
131
+ AI_GATEWAY_ADMIN: config.aiGateway.admin,
132
+ AI_GATEWAY_PASS: config.aiGateway.pass,
133
+ AI_EMBEDDING_MODEL: config.aiGateway.embeddingModel ?? 'openai/text-embedding-3-small',
134
+ REDIS_URL: config.redis.url,
135
+ LOG_LEVEL: config.logging?.level ?? 'info',
136
+ S3_ENDPOINT: config.s3.endpoint,
137
+ S3_BUCKET: config.s3.bucket,
138
+ S3_REGION: config.s3.region ?? 'garage',
139
+ S3_ACCESS_KEY_ID: config.s3.accessKeyId,
140
+ S3_SECRET_ACCESS_KEY: config.s3.secretAccessKey,
141
+ ATTACHMENT_URL_EXPIRES_IN: config.s3.attachmentUrlExpiresIn ?? 1800,
142
+ FIRECRAWL_API_KEY: config.firecrawl.apiKey,
143
+ FIRECRAWL_API_BASE_URL: config.firecrawl.apiBaseUrl,
144
+ MEMORY_SEARCH_K: config.memory?.searchK ?? 6,
145
+ })
146
+
147
+ await configureLogger(config.logging?.level ?? 'info')
148
+
149
+ const db = new SurrealDBServiceClass({
150
+ url: config.database.url,
151
+ namespace: config.database.namespace,
152
+ database: config.database.database,
153
+ username: config.database.username,
154
+ password: config.database.password,
155
+ })
156
+ setDatabaseService(db)
157
+
158
+ const redisManager = createRedisConnectionManager({ url: config.redis.url })
159
+ setRedisConnectionManager(redisManager)
160
+
161
+ configureAgents({
162
+ roster: config.agents.roster,
163
+ displayNames: config.agents.displayNames,
164
+ shortDisplayNames: config.agents.shortDisplayNames,
165
+ teamConsultParticipants: config.agents.teamConsultParticipants,
166
+ getCoreWorkstreamProfile: config.agents.getCoreWorkstreamProfile,
167
+ })
168
+ configureWorkstreams({ agentRoster: config.agents.roster, config: config.workstreams })
169
+
170
+ if (config.agents.createAgent || config.agents.buildAgentTools || config.agents.getAgentRuntimeConfig) {
171
+ configureAgentFactory({
172
+ createAgent: config.agents.createAgent ?? {},
173
+ buildAgentTools: config.agents.buildAgentTools,
174
+ getAgentRuntimeConfig: config.agents.getAgentRuntimeConfig,
175
+ pluginRuntime: config.pluginRuntime,
176
+ })
177
+ }
178
+
179
+ const { attachmentService } = await import('./services/attachment.service')
180
+ const { recentActivityService } = await import('./services/recent-activity.service')
181
+ const { recentActivityTitleService } = await import('./services/recent-activity-title.service')
182
+ const { executionPlanService } = await import('./services/execution-plan.service')
183
+ const { memoryService } = await import('./services/memory.service')
184
+ const { verifyMutatingApproval } = await import('./services/mutating-approval.service')
185
+ const { workstreamMessageService } = await import('./services/workstream-message.service')
186
+ const { workstreamService } = await import('./services/workstream.service')
187
+ const { workstreamTitleService } = await import('./services/workstream-title.service')
188
+ const {
189
+ createWorkstreamApprovalContinuationStream,
190
+ createWorkstreamTurnStream,
191
+ isApprovalContinuationRequest,
192
+ runWorkstreamTurnInBackground,
193
+ } = await import('./services/workstream-turn')
194
+ const { generatedDocumentStorageService } = await import('./storage/generated-document-storage.service')
195
+ const { startContextCompactionWorker } = await import('./queues/context-compaction.queue')
196
+ const { scheduleRecurringConsolidation, startMemoryConsolidationWorker } =
197
+ await import('./queues/memory-consolidation.queue')
198
+ const { startPostChatMemoryWorker } = await import('./queues/post-chat-memory.queue')
199
+ const { startRecentActivityTitleRefinementWorker } = await import('./queues/recent-activity-title-refinement.queue')
200
+ const { startRegularChatMemoryDigestWorker } = await import('./queues/regular-chat-memory-digest.queue')
201
+ const { startSkillExtractionWorker } = await import('./queues/skill-extraction.queue')
202
+
203
+ configureRuntimeExtensions({
204
+ adapters: config.runtimeAdapters,
205
+ turnHooks: config.turnHooks,
206
+ toolProviders: (config.toolProviders ?? {}) as never,
207
+ extraWorkers: config.extraWorkers,
208
+ })
209
+
210
+ const pluginRuntime = config.pluginRuntime ?? {}
211
+ const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
212
+ const schemaFiles = [
213
+ ...getBuiltInSchemaFiles(),
214
+ ...pluginContributions.flatMap((plugin) => plugin.schemaFiles),
215
+ ...(config.extraSchemaFiles ?? []),
216
+ ]
217
+ const contributionEnvKeys = [...lotaSdkEnvKeys, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
218
+ const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
219
+ const builtInWorkers = {
220
+ startContextCompactionWorker,
221
+ startMemoryConsolidationWorker,
222
+ startPostChatMemoryWorker,
223
+ startRecentActivityTitleRefinementWorker,
224
+ startRegularChatMemoryDigestWorker,
225
+ startSkillExtractionWorker,
226
+ scheduleRecurringConsolidation,
227
+ } satisfies LotaRuntimeBuiltInWorkers
228
+
229
+ return {
230
+ services: {
231
+ database: db,
232
+ databaseService: db,
233
+ redis: redisManager,
234
+ closeRedisConnection: async () => await redisManager.closeConnection(),
235
+ attachmentService,
236
+ generatedDocumentStorageService,
237
+ memoryService,
238
+ verifyMutatingApproval,
239
+ recentActivityService,
240
+ recentActivityTitleService,
241
+ executionPlanService,
242
+ workstreamMessageService,
243
+ workstreamService,
244
+ workstreamTitleService,
245
+ createWorkstreamApprovalContinuationStream,
246
+ createWorkstreamTurnStream,
247
+ isApprovalContinuationRequest,
248
+ runWorkstreamTurnInBackground,
249
+ },
250
+ redis: {
251
+ manager: redisManager,
252
+ getConnection: () => redisManager.getConnection(),
253
+ getConnectionForBullMQ: () => redisManager.getConnectionForBullMQ(),
254
+ closeConnection: async () => await redisManager.closeConnection(),
255
+ },
256
+ workers: { ...builtInWorkers, ...config.extraWorkers },
257
+ schemaFiles,
258
+ contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles },
259
+ config,
260
+ plugins: pluginRuntime,
261
+ async connectPluginDatabases() {
262
+ await connectPluginDatabases()
263
+ },
264
+ async connect() {
265
+ await db.connect()
266
+ },
267
+ async disconnect() {
268
+ await db.disconnect()
269
+ await redisManager.closeConnection()
270
+ },
271
+ }
272
+ }
273
+
274
+ function getBuiltInSchemaFiles(): URL[] {
275
+ return [
276
+ new URL('../infrastructure/schema/00_workstream.surql', import.meta.url),
277
+ new URL('../infrastructure/schema/01_memory.surql', import.meta.url),
278
+ new URL('../infrastructure/schema/02_execution_plan.surql', import.meta.url),
279
+ new URL('../infrastructure/schema/03_learned_skill.surql', import.meta.url),
280
+ new URL('../infrastructure/schema/04_runtime_bootstrap.surql', import.meta.url),
281
+ ]
282
+ }
283
+
284
+ function createPluginDatabaseConnector(pluginRuntime: Record<string, LotaPlugin>): () => Promise<void> {
285
+ return async () => {
286
+ for (const plugin of Object.values(pluginRuntime)) {
287
+ const services = plugin.services as Record<string, unknown>
288
+ const connectDatabase = services.connectDatabase
289
+ if (typeof connectDatabase !== 'function') {
290
+ continue
291
+ }
292
+
293
+ await Reflect.apply(connectDatabase, services, [])
294
+ }
295
+ }
296
+ }
297
+
298
+ export type { CoreWorkstreamProfile } from './config/agent-defaults'
299
+ export type { SurrealDBService } from './db/service'
300
+ export type { RedisConnectionManager } from './redis/connection'
301
+ export type { LotaPlugin, LotaPluginContributions } from './runtime/plugin-types'
302
+ export type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime/runtime-extensions'
@@ -0,0 +1,82 @@
1
+ import { Queue, Worker } from 'bullmq'
2
+ import type { Job } from 'bullmq'
3
+
4
+ import { serverLogger } from '../config/logger'
5
+ import { ensureRecordId } from '../db/record-id'
6
+ import { databaseService } from '../db/service'
7
+ import { TABLES } from '../db/tables'
8
+ import { getRedisConnectionForBullMQ } from '../redis'
9
+ import { contextCompactionService } from '../services/context-compaction.service'
10
+ import { workstreamService } from '../services/workstream.service'
11
+ import {
12
+ attachWorkerEvents,
13
+ createTracedWorkerProcessor,
14
+ createWorkerShutdown,
15
+ registerShutdownSignals,
16
+ } from '../workers/worker-utils'
17
+ import type { WorkerHandle } from '../workers/worker-utils'
18
+
19
+ interface ContextCompactionJob {
20
+ domain: 'workstream'
21
+ entityId: string
22
+ contextSize?: number
23
+ }
24
+
25
+ const CONTEXT_COMPACTION_QUEUE = 'context-compaction'
26
+
27
+ let _contextCompactionQueue: Queue<ContextCompactionJob> | null = null
28
+ function getContextCompactionQueue(): Queue<ContextCompactionJob> {
29
+ if (!_contextCompactionQueue) {
30
+ _contextCompactionQueue = new Queue<ContextCompactionJob>(CONTEXT_COMPACTION_QUEUE, {
31
+ connection: getRedisConnectionForBullMQ(),
32
+ defaultJobOptions: {
33
+ removeOnComplete: 200,
34
+ removeOnFail: 200,
35
+ attempts: 2,
36
+ backoff: { type: 'exponential', delay: 3_000 },
37
+ },
38
+ })
39
+ }
40
+ return _contextCompactionQueue
41
+ }
42
+
43
+ export async function enqueueContextCompaction(job: ContextCompactionJob) {
44
+ return await getContextCompactionQueue().add('compact', job, {
45
+ deduplication: { id: `compact:${job.domain}:${job.entityId}` },
46
+ })
47
+ }
48
+
49
+ async function processContextCompactionJob(job: Job<ContextCompactionJob>): Promise<void> {
50
+ await databaseService.connect()
51
+
52
+ const { entityId, contextSize } = job.data
53
+ const workstreamRef = ensureRecordId(entityId, TABLES.WORKSTREAM)
54
+ await workstreamService.setCompacting(workstreamRef, true)
55
+ try {
56
+ await contextCompactionService.compactWorkstreamHistory({ workstreamId: workstreamRef, contextSize })
57
+ } finally {
58
+ await workstreamService.setCompacting(workstreamRef, false)
59
+ }
60
+ }
61
+
62
+ export function startContextCompactionWorker(options: { registerSignals?: boolean } = {}): WorkerHandle {
63
+ const { registerSignals = import.meta.main } = options
64
+ const worker = new Worker(
65
+ CONTEXT_COMPACTION_QUEUE,
66
+ createTracedWorkerProcessor(CONTEXT_COMPACTION_QUEUE, processContextCompactionJob),
67
+ { connection: getRedisConnectionForBullMQ(), concurrency: 2, lockDuration: 300_000 },
68
+ )
69
+
70
+ attachWorkerEvents(worker, 'Context compaction', serverLogger)
71
+ const shutdown = createWorkerShutdown(worker, 'Context compaction', serverLogger)
72
+
73
+ if (registerSignals) {
74
+ registerShutdownSignals({ name: 'Context compaction', shutdown, logger: serverLogger })
75
+ }
76
+
77
+ return { worker, shutdown }
78
+ }
79
+
80
+ if (import.meta.main) {
81
+ startContextCompactionWorker()
82
+ }
@@ -0,0 +1,118 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ import { Queue, Worker } from 'bullmq'
4
+ import type IORedis from 'ioredis'
5
+
6
+ import type { chatLogger } from '../config/logger'
7
+ import { attachWorkerEvents, createWorkerShutdown, registerShutdownSignals } from '../workers/worker-utils'
8
+ import type { WorkerHandle } from '../workers/worker-utils'
9
+
10
+ export type DocumentSourceChannel = string
11
+
12
+ export interface DocumentProcessorAttachmentRef {
13
+ storageKey: string
14
+ name: string
15
+ contentType: string
16
+ sizeBytes?: number
17
+ }
18
+
19
+ export interface DocumentProcessorJob {
20
+ orgId: string
21
+ source: DocumentSourceChannel
22
+ sourceId: string
23
+ title: string
24
+ contentType?: string
25
+ sourceCanonicalKey: string
26
+ sourceVersionKey: string
27
+ text?: string
28
+ attachment?: DocumentProcessorAttachmentRef
29
+ metadata?: Record<string, unknown>
30
+ }
31
+
32
+ const DEFAULT_DOCUMENT_PROCESSOR_QUEUE = 'document-processor'
33
+ const DEFAULT_WORKER_NAME = 'Document processor'
34
+
35
+ export function buildDocumentProcessorJobId(
36
+ job: Pick<
37
+ DocumentProcessorJob,
38
+ 'orgId' | 'source' | 'sourceId' | 'sourceCanonicalKey' | 'sourceVersionKey' | 'title'
39
+ >,
40
+ ): string {
41
+ const digest = createHash('sha256')
42
+ .update(
43
+ JSON.stringify({
44
+ orgId: job.orgId,
45
+ source: job.source,
46
+ sourceId: job.sourceId,
47
+ sourceCanonicalKey: job.sourceCanonicalKey,
48
+ sourceVersionKey: job.sourceVersionKey,
49
+ title: job.title,
50
+ }),
51
+ )
52
+ .digest('hex')
53
+
54
+ return `doc__${digest}`
55
+ }
56
+
57
+ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcessorJob>(params: {
58
+ getConnectionForBullMQ: () => IORedis
59
+ getWorkerPath: () => string
60
+ logger: typeof chatLogger
61
+ queueName?: string
62
+ workerName?: string
63
+ concurrency?: number
64
+ lockDuration?: number
65
+ }): {
66
+ enqueue: (job: TJob) => Promise<unknown>
67
+ startWorker: (options?: { registerSignals?: boolean }) => WorkerHandle
68
+ } {
69
+ const queueName = params.queueName ?? DEFAULT_DOCUMENT_PROCESSOR_QUEUE
70
+ const workerName = params.workerName ?? DEFAULT_WORKER_NAME
71
+ const concurrency = params.concurrency ?? 4
72
+ const lockDuration = params.lockDuration ?? 300_000
73
+ const jobName = 'process-document' as Parameters<Queue<TJob, unknown, string>['add']>[0]
74
+ const toQueueData = (job: TJob): Parameters<Queue<TJob, unknown, string>['add']>[1] =>
75
+ job as Parameters<Queue<TJob, unknown, string>['add']>[1]
76
+ let queue: Queue<TJob, unknown, string> | null = null
77
+
78
+ const getQueue = (): Queue<TJob, unknown, string> => {
79
+ if (queue) {
80
+ return queue
81
+ }
82
+
83
+ queue = new Queue<TJob, unknown, string>(queueName, {
84
+ connection: params.getConnectionForBullMQ(),
85
+ defaultJobOptions: {
86
+ removeOnComplete: 200,
87
+ removeOnFail: 200,
88
+ attempts: 3,
89
+ backoff: { type: 'exponential', delay: 1000 },
90
+ },
91
+ })
92
+
93
+ return queue
94
+ }
95
+
96
+ return {
97
+ enqueue: async (job) =>
98
+ await getQueue().add(jobName, toQueueData(job), { jobId: buildDocumentProcessorJobId(job) }),
99
+ startWorker: (options = {}) => {
100
+ const { registerSignals = import.meta.main } = options
101
+ const worker = new Worker(queueName, params.getWorkerPath(), {
102
+ connection: params.getConnectionForBullMQ(),
103
+ concurrency,
104
+ lockDuration,
105
+ })
106
+
107
+ attachWorkerEvents(worker, workerName, params.logger)
108
+
109
+ const shutdown = createWorkerShutdown(worker, workerName, params.logger)
110
+
111
+ if (registerSignals) {
112
+ registerShutdownSignals({ name: workerName, shutdown, logger: params.logger })
113
+ }
114
+
115
+ return { worker, shutdown }
116
+ },
117
+ }
118
+ }
@@ -0,0 +1,65 @@
1
+ import { Queue, Worker } from 'bullmq'
2
+
3
+ import { serverLogger } from '../config/logger'
4
+ import { getRedisConnectionForBullMQ } from '../redis'
5
+ import {
6
+ attachWorkerEvents,
7
+ getWorkerPath,
8
+ createWorkerShutdown,
9
+ registerShutdownSignals,
10
+ } from '../workers/worker-utils'
11
+ import type { WorkerHandle } from '../workers/worker-utils'
12
+
13
+ export interface MemoryConsolidationJob {
14
+ scopeId?: string
15
+ }
16
+
17
+ const MEMORY_CONSOLIDATION_QUEUE = 'memory-consolidation'
18
+
19
+ let _memoryConsolidationQueue: Queue<MemoryConsolidationJob> | null = null
20
+ function getMemoryConsolidationQueue(): Queue<MemoryConsolidationJob> {
21
+ if (!_memoryConsolidationQueue) {
22
+ _memoryConsolidationQueue = new Queue<MemoryConsolidationJob>(MEMORY_CONSOLIDATION_QUEUE, {
23
+ connection: getRedisConnectionForBullMQ(),
24
+ defaultJobOptions: {
25
+ removeOnComplete: 50,
26
+ removeOnFail: 50,
27
+ attempts: 2,
28
+ backoff: { type: 'exponential', delay: 5000 },
29
+ },
30
+ })
31
+ }
32
+ return _memoryConsolidationQueue
33
+ }
34
+
35
+ export async function scheduleRecurringConsolidation() {
36
+ await getMemoryConsolidationQueue().add(
37
+ 'consolidate',
38
+ {},
39
+ { repeat: { every: 24 * 60 * 60 * 1000 }, jobId: 'memory-consolidation-recurring' },
40
+ )
41
+ }
42
+
43
+ export function startMemoryConsolidationWorker(options: { registerSignals?: boolean } = {}): WorkerHandle {
44
+ const { registerSignals = import.meta.main } = options
45
+ const processorPath = getWorkerPath('memory-consolidation.worker.ts')
46
+ const worker = new Worker(MEMORY_CONSOLIDATION_QUEUE, processorPath, {
47
+ connection: getRedisConnectionForBullMQ(),
48
+ lockDuration: 600_000,
49
+ concurrency: 1,
50
+ })
51
+
52
+ attachWorkerEvents(worker, 'Memory consolidation', serverLogger)
53
+
54
+ const shutdown = createWorkerShutdown(worker, 'Memory consolidation', serverLogger)
55
+
56
+ if (registerSignals) {
57
+ registerShutdownSignals({ name: 'Memory consolidation', shutdown, logger: serverLogger })
58
+ }
59
+
60
+ return { worker, shutdown }
61
+ }
62
+
63
+ if (import.meta.main) {
64
+ startMemoryConsolidationWorker()
65
+ }
@@ -0,0 +1,128 @@
1
+ type OrganizationOnboardStatus = string
2
+ import { Queue, Worker } from 'bullmq'
3
+ import type { Job } from 'bullmq'
4
+
5
+ import { serverLogger } from '../config/logger'
6
+ import { databaseService } from '../db/service'
7
+ import { getRedisConnectionForBullMQ } from '../redis'
8
+ import { memoryService } from '../services/memory.service'
9
+ import {
10
+ attachWorkerEvents,
11
+ createTracedWorkerProcessor,
12
+ createWorkerShutdown,
13
+ registerShutdownSignals,
14
+ } from '../workers/worker-utils'
15
+ import type { WorkerHandle } from '../workers/worker-utils'
16
+
17
+ interface PostChatMemoryMessage {
18
+ role: 'user' | 'agent'
19
+ content: string
20
+ agentName?: string
21
+ }
22
+
23
+ interface PostChatMemoryExtractionJob {
24
+ orgId: string
25
+ workstreamId: string
26
+ sourceId: string
27
+ onboardStatus?: OrganizationOnboardStatus
28
+ userMessage: string
29
+ historyMessages: PostChatMemoryMessage[]
30
+ agentMessages: Array<{ content: string; agentName?: string }>
31
+ memoryBlock?: string
32
+ attachmentContext?: string
33
+ }
34
+
35
+ const POST_CHAT_MEMORY_QUEUE = 'post-chat-memory'
36
+ const POST_CHAT_MEMORY_CONCURRENCY = 3
37
+ const POST_CHAT_MEMORY_LOCK_DURATION_MS = 900_000
38
+ const POST_CHAT_MEMORY_MAX_STALLED_COUNT = 10
39
+ const POST_CHAT_MEMORY_STALLED_INTERVAL_MS = 120_000
40
+
41
+ let _postChatMemoryQueue: Queue<PostChatMemoryExtractionJob> | null = null
42
+ function getPostChatMemoryQueue(): Queue<PostChatMemoryExtractionJob> {
43
+ if (!_postChatMemoryQueue) {
44
+ _postChatMemoryQueue = new Queue<PostChatMemoryExtractionJob>(POST_CHAT_MEMORY_QUEUE, {
45
+ connection: getRedisConnectionForBullMQ(),
46
+ defaultJobOptions: {
47
+ removeOnComplete: 200,
48
+ removeOnFail: 200,
49
+ attempts: 3,
50
+ backoff: { type: 'exponential', delay: 2_000 },
51
+ },
52
+ })
53
+ }
54
+ return _postChatMemoryQueue
55
+ }
56
+
57
+ export async function enqueuePostChatMemory(job: PostChatMemoryExtractionJob) {
58
+ return await getPostChatMemoryQueue().add('extract-memory', job)
59
+ }
60
+
61
+ async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>): Promise<void> {
62
+ await databaseService.connect()
63
+
64
+ const data = job.data
65
+ const userMessage = data.userMessage.trim()
66
+ const agentMessages = data.agentMessages
67
+ .map((item) => ({
68
+ content: item.content.trim(),
69
+ ...(typeof item.agentName === 'string' && item.agentName.trim() ? { agentName: item.agentName.trim() } : {}),
70
+ }))
71
+ .filter((item) => item.content.length > 0)
72
+
73
+ if (!userMessage || agentMessages.length === 0) return
74
+
75
+ const joinedOutput = agentMessages
76
+ .map((item) => (item.agentName ? `[${item.agentName}] ${item.content}` : item.content))
77
+ .join('\n\n')
78
+
79
+ const uniqueAgentNames = [
80
+ ...new Set(
81
+ agentMessages
82
+ .map((item) => item.agentName)
83
+ .filter((value): value is string => typeof value === 'string' && value.length > 0),
84
+ ),
85
+ ]
86
+
87
+ await memoryService.addConversationMemories({
88
+ orgId: data.orgId,
89
+ input: userMessage,
90
+ output: joinedOutput,
91
+ sourceId: data.sourceId,
92
+ onboardStatus: data.onboardStatus,
93
+ ...(uniqueAgentNames.length > 0 ? { agentName: uniqueAgentNames[0] } : {}),
94
+ historyMessages: data.historyMessages,
95
+ memoryBlock: data.memoryBlock,
96
+ attachmentContext: data.attachmentContext,
97
+ agentNames: uniqueAgentNames,
98
+ })
99
+ }
100
+
101
+ export function startPostChatMemoryWorker(options: { registerSignals?: boolean } = {}): WorkerHandle {
102
+ const { registerSignals = import.meta.main } = options
103
+ const worker = new Worker(
104
+ POST_CHAT_MEMORY_QUEUE,
105
+ createTracedWorkerProcessor(POST_CHAT_MEMORY_QUEUE, processPostChatMemoryJob),
106
+ {
107
+ connection: getRedisConnectionForBullMQ(),
108
+ concurrency: POST_CHAT_MEMORY_CONCURRENCY,
109
+ lockDuration: POST_CHAT_MEMORY_LOCK_DURATION_MS,
110
+ maxStalledCount: POST_CHAT_MEMORY_MAX_STALLED_COUNT,
111
+ stalledInterval: POST_CHAT_MEMORY_STALLED_INTERVAL_MS,
112
+ },
113
+ )
114
+
115
+ attachWorkerEvents(worker, 'Post-chat memory', serverLogger)
116
+
117
+ const shutdown = createWorkerShutdown(worker, 'Post-chat memory', serverLogger)
118
+
119
+ if (registerSignals) {
120
+ registerShutdownSignals({ name: 'Post-chat memory', shutdown, logger: serverLogger })
121
+ }
122
+
123
+ return { worker, shutdown }
124
+ }
125
+
126
+ if (import.meta.main) {
127
+ startPostChatMemoryWorker()
128
+ }