@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,11 +1,15 @@
1
1
  import type { ToolSet } from 'ai'
2
+ import { Config, ConfigProvider, Effect, Option, Redacted } from 'effect'
2
3
  import { z } from 'zod'
3
4
 
4
5
  import type { CoreThreadProfile } from '../config/agent-defaults'
5
6
  import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
7
+ import { DEFAULT_AI_GATEWAY_URL } from '../config/constants'
6
8
  import { OPENROUTER_FAST_RERANK_MODEL_ID } from '../config/model-constants'
7
9
  import type { LotaThreadConfig, ThreadBootstrapWelcomeConfig } from '../config/thread-defaults'
8
10
  import type { RecordIdRef } from '../db/record-id'
11
+ import { getCurrentRuntime } from '../effect/runtime-ref'
12
+ import { RuntimeConfigServiceTag } from '../effect/services'
9
13
  import type { NotificationService } from '../services/notification.service'
10
14
  import { isRecord } from '../utils/string'
11
15
  import type { GraphDesigner } from './graph-designer'
@@ -14,6 +18,11 @@ import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime-extens
14
18
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
15
19
 
16
20
  const logLevelValues = ['trace', 'debug', 'info', 'warning', 'error', 'fatal'] as const
21
+ const nonEmptyStringSchema = z.string().trim().min(1)
22
+ const stringRecordSchema = z.record(z.string(), z.string())
23
+ function objectRecordSchema<T extends object>() {
24
+ return z.custom<T>(isRecord)
25
+ }
17
26
 
18
27
  function isStringOrUrl(value: unknown): value is string | URL {
19
28
  return typeof value === 'string' || value instanceof URL
@@ -23,26 +32,14 @@ function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
23
32
  return typeof value === 'function'
24
33
  }
25
34
 
26
- function isStringRecord(value: unknown): value is Record<string, string> {
27
- return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string')
35
+ function isToolSet(value: unknown): value is ToolSet {
36
+ return isRecord(value)
28
37
  }
29
38
 
30
39
  function isAgentFactoryRegistry(value: unknown): value is AgentFactory {
31
40
  return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'function')
32
41
  }
33
42
 
34
- function isPluginRuntimeRecord(value: unknown): value is Record<string, LotaPlugin> {
35
- return isRecord(value)
36
- }
37
-
38
- function isSystemExecutorRecord(value: unknown): value is Record<string, SystemNodeExecutor> {
39
- return isRecord(value)
40
- }
41
-
42
- function isToolProviderRecord(value: unknown): value is Record<string, unknown> {
43
- return isRecord(value)
44
- }
45
-
46
43
  function isNotificationService(value: unknown): value is NotificationService {
47
44
  return isRecord(value) && isFunction(value.notify) && isFunction(value.remind) && isFunction(value.escalate)
48
45
  }
@@ -146,7 +143,7 @@ function isSocialChatConfig(value: unknown): value is LotaRuntimeSocialChatConfi
146
143
  }
147
144
 
148
145
  const threadBootstrapWelcomeConfigSchema = z.object({
149
- defaultAgentId: z.string().trim().min(1),
146
+ defaultAgentId: nonEmptyStringSchema,
150
147
  buildMessageText: z.custom<ThreadBootstrapWelcomeConfig['buildMessageText']>(isFunction, {
151
148
  error: 'onboardingWelcome.buildMessageText must be a function',
152
149
  }),
@@ -156,9 +153,9 @@ const threadConfigSchema = z
156
153
  .object({
157
154
  bootstrap: z
158
155
  .object({
159
- onboardingDefaultAgents: z.array(z.string().trim().min(1)).optional(),
160
- completedDefaultAgents: z.array(z.string().trim().min(1)).optional(),
161
- threadTypesAfterOnboarding: z.array(z.string().trim().min(1)).optional(),
156
+ onboardingDefaultAgents: z.array(nonEmptyStringSchema).optional(),
157
+ completedDefaultAgents: z.array(nonEmptyStringSchema).optional(),
158
+ threadTypesAfterOnboarding: z.array(nonEmptyStringSchema).optional(),
162
159
  ensureDefaultGroupOnCompleted: z.boolean().optional(),
163
160
  onboardingWelcome: threadBootstrapWelcomeConfigSchema.optional(),
164
161
  })
@@ -169,19 +166,13 @@ const threadConfigSchema = z
169
166
 
170
167
  const agentsConfigSchema = z
171
168
  .object({
172
- roster: z.array(z.string().trim().min(1)).min(1),
173
- leadAgentId: z.string().trim().min(1),
174
- displayNames: z.custom<Record<string, string>>(isStringRecord, {
175
- error: 'agents.displayNames must be a string record',
176
- }),
177
- shortDisplayNames: z
178
- .custom<Record<string, string>>(isStringRecord, { error: 'agents.shortDisplayNames must be a string record' })
179
- .optional(),
180
- descriptions: z
181
- .custom<Record<string, string>>(isStringRecord, { error: 'agents.descriptions must be a string record' })
182
- .optional(),
183
- routerModelId: z.string().trim().min(1).optional(),
184
- teamConsultParticipants: z.array(z.string().trim().min(1)),
169
+ roster: z.array(nonEmptyStringSchema).min(1),
170
+ leadAgentId: nonEmptyStringSchema,
171
+ displayNames: stringRecordSchema,
172
+ shortDisplayNames: stringRecordSchema.optional(),
173
+ descriptions: stringRecordSchema.optional(),
174
+ routerModelId: nonEmptyStringSchema.optional(),
175
+ teamConsultParticipants: z.array(nonEmptyStringSchema),
185
176
  getCoreThreadProfile: z
186
177
  .custom<(coreType: string) => CoreThreadProfile>(isFunction, {
187
178
  error: 'agents.getCoreThreadProfile must be a function',
@@ -222,24 +213,25 @@ export type MemoryRerankerStrategy = z.infer<typeof MemoryRerankerStrategySchema
222
213
 
223
214
  export const LotaRuntimeConfigSchema = z.object({
224
215
  database: z.object({
225
- url: z.string().trim().min(1),
226
- namespace: z.string().trim().min(1),
227
- username: z.string().trim().min(1),
228
- password: z.string().trim().min(1),
216
+ url: nonEmptyStringSchema,
217
+ namespace: nonEmptyStringSchema,
218
+ username: nonEmptyStringSchema,
219
+ password: nonEmptyStringSchema,
229
220
  }),
230
- redis: z.object({ url: z.string().trim().min(1) }),
221
+ redis: z.object({ url: nonEmptyStringSchema }),
231
222
  aiGateway: z.object({
232
- url: z.string().trim().min(1),
233
- key: z.string().trim().min(1),
234
- embeddingModel: z.string().trim().min(1).default('openai/text-embedding-3-small'),
235
- openRouterApiKey: z.string().trim().min(1).optional(),
223
+ url: nonEmptyStringSchema,
224
+ key: nonEmptyStringSchema,
225
+ embeddingModel: nonEmptyStringSchema.default('openai/text-embedding-3-small'),
226
+ openRouterApiKey: nonEmptyStringSchema.optional(),
227
+ maxConcurrency: z.coerce.number().int().positive().default(8),
236
228
  }),
237
229
  s3: z.object({
238
- endpoint: z.string().trim().min(1),
239
- bucket: z.string().trim().min(1),
240
- region: z.string().trim().min(1).default('garage'),
241
- accessKeyId: z.string().trim().min(1),
242
- secretAccessKey: z.string().trim().min(1),
230
+ endpoint: nonEmptyStringSchema,
231
+ bucket: nonEmptyStringSchema,
232
+ region: nonEmptyStringSchema.default('garage'),
233
+ accessKeyId: nonEmptyStringSchema,
234
+ secretAccessKey: nonEmptyStringSchema,
243
235
  attachmentUrlExpiresIn: z.coerce.number().positive().default(1800),
244
236
  }),
245
237
  firecrawl: z.object({
@@ -266,16 +258,25 @@ export const LotaRuntimeConfigSchema = z.object({
266
258
  }),
267
259
  threads: threadConfigSchema.default({}),
268
260
  agents: agentsConfigSchema,
269
- toolProviders: z.custom<Record<string, unknown>>(isToolProviderRecord).optional(),
261
+ toolProviders: z.custom<ToolSet>(isToolSet, { error: 'toolProviders must be a tool registry object' }).optional(),
270
262
  extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
271
263
  extraWorkers: z.custom<LotaRuntimeWorkerExtensions>(isWorkerExtensionRecord).optional(),
272
- pluginRuntime: z.custom<Record<string, LotaPlugin>>(isPluginRuntimeRecord).optional(),
273
- systemExecutors: z.custom<Record<string, SystemNodeExecutor>>(isSystemExecutorRecord).optional(),
264
+ pluginRuntime: objectRecordSchema<Record<string, LotaPlugin>>().optional(),
265
+ systemExecutors: objectRecordSchema<Record<string, SystemNodeExecutor>>().optional(),
274
266
  notificationService: z.custom<NotificationService>(isNotificationService).optional(),
275
- runtimeAdapters: z.custom<LotaRuntimeAdapters>(isRecord).optional(),
276
- turnHooks: z.custom<LotaRuntimeTurnHooks>(isRecord).optional(),
267
+ runtimeAdapters: objectRecordSchema<LotaRuntimeAdapters>().optional(),
268
+ turnHooks: objectRecordSchema<LotaRuntimeTurnHooks>().optional(),
277
269
  graphDesigner: z.custom<GraphDesigner>(isGraphDesigner).optional(),
278
270
  socialChat: z.custom<LotaRuntimeSocialChatConfig>(isSocialChatConfig).optional(),
271
+ observability: z
272
+ .object({
273
+ otlpBaseUrl: nonEmptyStringSchema.optional(),
274
+ otlpHeaders: stringRecordSchema.optional(),
275
+ serviceName: nonEmptyStringSchema.default('lota-sdk'),
276
+ serviceVersion: nonEmptyStringSchema.optional(),
277
+ devToolsUrl: nonEmptyStringSchema.optional(),
278
+ })
279
+ .default({ serviceName: 'lota-sdk' }),
279
280
  })
280
281
 
281
282
  export type LotaRuntimeConfig = z.input<typeof LotaRuntimeConfigSchema>
@@ -301,6 +302,7 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
301
302
  'AI_GATEWAY_KEY',
302
303
  'AI_EMBEDDING_MODEL',
303
304
  'OPENROUTER_API_KEY',
305
+ 'AI_GATEWAY_MAX_CONCURRENCY',
304
306
  'S3_ENDPOINT',
305
307
  'S3_BUCKET',
306
308
  'S3_REGION',
@@ -313,28 +315,115 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
313
315
  'MEMORY_SEARCH_K',
314
316
  'MEMORY_RERANKER_STRATEGY',
315
317
  'MEMORY_RERANKER_MODEL_ID',
318
+ 'OTLP_BASE_URL',
319
+ 'OTLP_SERVICE_NAME',
320
+ 'OTLP_SERVICE_VERSION',
321
+ 'DEVTOOLS_URL',
316
322
  ])
317
323
 
318
- let runtimeConfig: ResolvedLotaRuntimeConfig | null = null
319
-
320
324
  export function parseLotaRuntimeConfig(config: LotaRuntimeConfig): ResolvedLotaRuntimeConfig {
321
325
  return LotaRuntimeConfigSchema.parse(config)
322
326
  }
323
327
 
324
- export function configureRuntimeConfig(config: ResolvedLotaRuntimeConfig): void {
325
- runtimeConfig = config
328
+ export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
329
+ return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
326
330
  }
327
331
 
328
- export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
329
- if (!runtimeConfig) {
330
- throw new Error('Lota runtime config not configured. Call createLotaRuntime() first.')
331
- }
332
+ export const lotaRuntimeEnvConfig = Config.all({
333
+ surrealdbUrl: Config.string('SURREALDB_URL'),
334
+ surrealdbNamespace: Config.string('SURREALDB_NAMESPACE'),
335
+ surrealdbUser: Config.string('SURREALDB_USER'),
336
+ surrealdbPassword: Config.redacted('SURREALDB_PASSWORD'),
337
+ redisUrl: Config.string('REDIS_URL'),
338
+ aiGatewayUrl: Config.string('AI_GATEWAY_URL').pipe(Config.withDefault(DEFAULT_AI_GATEWAY_URL)),
339
+ aiGatewayKey: Config.redacted('AI_GATEWAY_KEY'),
340
+ aiEmbeddingModel: Config.string('AI_EMBEDDING_MODEL').pipe(Config.withDefault('openai/text-embedding-3-small')),
341
+ openRouterApiKey: Config.redacted('OPENROUTER_API_KEY').pipe(Config.option),
342
+ aiGatewayMaxConcurrency: Config.number('AI_GATEWAY_MAX_CONCURRENCY').pipe(Config.withDefault(8)),
343
+ s3Endpoint: Config.string('S3_ENDPOINT'),
344
+ s3Bucket: Config.string('S3_BUCKET'),
345
+ s3Region: Config.string('S3_REGION').pipe(Config.withDefault('garage')),
346
+ s3AccessKeyId: Config.redacted('S3_ACCESS_KEY_ID'),
347
+ s3SecretAccessKey: Config.redacted('S3_SECRET_ACCESS_KEY'),
348
+ attachmentUrlExpiresIn: Config.number('ATTACHMENT_URL_EXPIRES_IN').pipe(Config.withDefault(1800)),
349
+ firecrawlApiKey: Config.redacted('FIRECRAWL_API_KEY'),
350
+ firecrawlApiBaseUrl: Config.string('FIRECRAWL_API_BASE_URL').pipe(Config.option),
351
+ logLevel: Config.string('LOG_LEVEL').pipe(Config.withDefault('info')),
352
+ memorySearchK: Config.number('MEMORY_SEARCH_K').pipe(Config.withDefault(6)),
353
+ memoryRerankerStrategy: Config.string('MEMORY_RERANKER_STRATEGY').pipe(Config.withDefault('rerank')),
354
+ memoryRerankerModelId: Config.string('MEMORY_RERANKER_MODEL_ID').pipe(
355
+ Config.withDefault(OPENROUTER_FAST_RERANK_MODEL_ID),
356
+ ),
357
+ otlpBaseUrl: Config.string('OTLP_BASE_URL').pipe(Config.option),
358
+ otlpServiceName: Config.string('OTLP_SERVICE_NAME').pipe(Config.withDefault('lota-sdk')),
359
+ otlpServiceVersion: Config.string('OTLP_SERVICE_VERSION').pipe(Config.option),
360
+ devToolsUrl: Config.string('DEVTOOLS_URL').pipe(Config.option),
361
+ })
332
362
 
333
- return runtimeConfig
363
+ export type LotaRuntimeEnvironmentOverrides = Omit<
364
+ LotaRuntimeConfig,
365
+ 'database' | 'redis' | 'aiGateway' | 's3' | 'firecrawl' | 'logging' | 'memory'
366
+ > & { logging?: LotaRuntimeConfig['logging']; memory?: LotaRuntimeConfig['memory'] }
367
+
368
+ export function loadLotaRuntimeConfigFromEnv(
369
+ overrides: LotaRuntimeEnvironmentOverrides,
370
+ options: { configProvider?: ConfigProvider.ConfigProvider } = {},
371
+ ) {
372
+ return lotaRuntimeEnvConfig
373
+ .parse(options.configProvider ?? ConfigProvider.fromEnv())
374
+ .pipe(
375
+ Effect.map((env) =>
376
+ parseLotaRuntimeConfig({
377
+ ...overrides,
378
+ database: {
379
+ url: env.surrealdbUrl,
380
+ namespace: env.surrealdbNamespace,
381
+ username: env.surrealdbUser,
382
+ password: Redacted.value(env.surrealdbPassword),
383
+ },
384
+ redis: { url: env.redisUrl },
385
+ aiGateway: {
386
+ url: env.aiGatewayUrl,
387
+ key: Redacted.value(env.aiGatewayKey),
388
+ embeddingModel: env.aiEmbeddingModel,
389
+ maxConcurrency: env.aiGatewayMaxConcurrency,
390
+ ...(Option.isSome(env.openRouterApiKey)
391
+ ? { openRouterApiKey: Redacted.value(env.openRouterApiKey.value) }
392
+ : {}),
393
+ },
394
+ s3: {
395
+ endpoint: env.s3Endpoint,
396
+ bucket: env.s3Bucket,
397
+ region: env.s3Region,
398
+ accessKeyId: Redacted.value(env.s3AccessKeyId),
399
+ secretAccessKey: Redacted.value(env.s3SecretAccessKey),
400
+ attachmentUrlExpiresIn: env.attachmentUrlExpiresIn,
401
+ },
402
+ firecrawl: {
403
+ apiKey: Redacted.value(env.firecrawlApiKey),
404
+ ...(Option.isSome(env.firecrawlApiBaseUrl) ? { apiBaseUrl: env.firecrawlApiBaseUrl.value } : {}),
405
+ },
406
+ logging: overrides.logging ?? { level: z.enum(logLevelValues).parse(env.logLevel) },
407
+ memory: {
408
+ searchK: env.memorySearchK,
409
+ rerankerStrategy: env.memoryRerankerStrategy as MemoryRerankerStrategy,
410
+ rerankerModelId: env.memoryRerankerModelId,
411
+ ...overrides.memory,
412
+ },
413
+ observability: {
414
+ serviceName: env.otlpServiceName,
415
+ ...(Option.isSome(env.otlpBaseUrl) ? { otlpBaseUrl: env.otlpBaseUrl.value } : {}),
416
+ ...(Option.isSome(env.otlpServiceVersion) ? { serviceVersion: env.otlpServiceVersion.value } : {}),
417
+ ...(Option.isSome(env.devToolsUrl) ? { devToolsUrl: env.devToolsUrl.value } : {}),
418
+ ...overrides.observability,
419
+ },
420
+ }),
421
+ ),
422
+ )
334
423
  }
335
424
 
336
- export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>): WorkerBootstrapEnv {
337
- return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
425
+ export function getRuntimeConfig(): ResolvedLotaRuntimeConfig {
426
+ return getCurrentRuntime().runSync(Effect.service(RuntimeConfigServiceTag))
338
427
  }
339
428
 
340
429
  export type { LotaThreadConfig }
@@ -8,6 +8,13 @@ import type {
8
8
  import type { ToolSet } from 'ai'
9
9
 
10
10
  import type { RecordIdRef } from '../db/record-id'
11
+ import { resolveLotaService } from '../effect/runtime'
12
+ import {
13
+ RuntimeAdaptersServiceTag,
14
+ RuntimeWorkerExtensionsServiceTag,
15
+ ToolProvidersServiceTag,
16
+ TurnHooksServiceTag,
17
+ } from '../effect/services'
11
18
  import type { ReadableUploadMetadata } from '../storage/attachment-types'
12
19
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
13
20
 
@@ -193,54 +200,45 @@ export interface LotaRuntimeAdapters {
193
200
  withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
194
201
  }
195
202
 
196
- interface RuntimeExtensionsState {
203
+ interface RuntimeExtensionsAccessors {
197
204
  adapters: LotaRuntimeAdapters
198
205
  turnHooks: LotaRuntimeTurnHooks
199
206
  toolProviders: ToolSet
200
207
  extraWorkers: LotaRuntimeWorkerExtensions
201
208
  }
202
209
 
203
- const EMPTY_TOOLS = {} as ToolSet
210
+ let runtimeExtensionsAccessors: RuntimeExtensionsAccessors | null = null
204
211
 
205
- let runtimeExtensionsState: RuntimeExtensionsState = {
206
- adapters: {},
207
- turnHooks: {},
208
- toolProviders: EMPTY_TOOLS,
209
- extraWorkers: {},
212
+ export function configureRuntimeExtensionsAccessors(accessors: RuntimeExtensionsAccessors): void {
213
+ runtimeExtensionsAccessors = accessors
210
214
  }
211
215
 
212
- export function configureRuntimeExtensions(params: {
213
- adapters?: LotaRuntimeAdapters
214
- turnHooks?: LotaRuntimeTurnHooks
215
- toolProviders?: ToolSet
216
- extraWorkers?: LotaRuntimeWorkerExtensions
217
- }): void {
218
- runtimeExtensionsState = {
219
- adapters: params.adapters ?? {},
220
- turnHooks: params.turnHooks ?? {},
221
- toolProviders: params.toolProviders ?? EMPTY_TOOLS,
222
- extraWorkers: params.extraWorkers ?? {},
223
- }
216
+ export function clearRuntimeExtensionsAccessors(): void {
217
+ runtimeExtensionsAccessors = null
224
218
  }
225
219
 
226
220
  export function getRuntimeAdapters(): LotaRuntimeAdapters {
227
- return runtimeExtensionsState.adapters
221
+ return runtimeExtensionsAccessors?.adapters ?? resolveLotaService(RuntimeAdaptersServiceTag)
228
222
  }
229
223
 
230
224
  export function getTurnHooks(): LotaRuntimeTurnHooks {
231
- return runtimeExtensionsState.turnHooks
225
+ return runtimeExtensionsAccessors?.turnHooks ?? resolveLotaService(TurnHooksServiceTag)
232
226
  }
233
227
 
234
228
  export function getToolProviders(): ToolSet {
235
- return runtimeExtensionsState.toolProviders
229
+ return runtimeExtensionsAccessors?.toolProviders ?? resolveLotaService(ToolProvidersServiceTag)
236
230
  }
237
231
 
238
232
  export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) | undefined {
239
- return runtimeExtensionsState.adapters.connectPluginDatabases
233
+ return getRuntimeAdapters().connectPluginDatabases
234
+ }
235
+
236
+ export function getExtraWorkers(): LotaRuntimeWorkerExtensions {
237
+ return runtimeExtensionsAccessors?.extraWorkers ?? resolveLotaService(RuntimeWorkerExtensionsServiceTag)
240
238
  }
241
239
 
242
- export async function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
243
- const adapter = runtimeExtensionsState.adapters.withWorkspaceMemoryLock
240
+ export function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
241
+ const adapter = getRuntimeAdapters().withWorkspaceMemoryLock
244
242
  if (!adapter) {
245
243
  return fn()
246
244
  }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Plugin database connect/disconnect helpers and runtime disconnect assembly.
3
+ *
4
+ * The plugin connect/disconnect helpers run inside the supplied
5
+ * `ManagedRuntime` so spans, logging, and layer context stay consistent with
6
+ * the rest of the SDK runtime. `createRuntimeDisconnect` instead drives the
7
+ * shutdown sequence through the caller-supplied `runPromiseWithCurrentContext`
8
+ * (currently `Effect.runPromiseWith(Context.empty())`), which intentionally
9
+ * does not preserve the SDK runtime context — by then the runtime is being
10
+ * torn down.
11
+ */
12
+
13
+ import type { ManagedRuntime } from 'effect'
14
+ import { Effect } from 'effect'
15
+
16
+ import { effectTryPromise } from '../effect/helpers'
17
+ import type { LotaPlugin } from './plugin-types'
18
+
19
+ // eslint-disable-next-line typescript-eslint/no-explicit-any -- ManagedRuntime is contravariant in R; `any` is the only valid wildcard
20
+ type SdkManagedRuntime = ManagedRuntime.ManagedRuntime<any, any>
21
+
22
+ function getPluginLifecycleServices(plugin: LotaPlugin): Record<string, unknown> {
23
+ return plugin.services
24
+ }
25
+
26
+ /**
27
+ * Build a plugin database connector that iterates the configured plugins and
28
+ * calls each `services.connectDatabase()` once, tracking completion in
29
+ * `connectedPluginDatabases` so repeated calls are idempotent.
30
+ */
31
+ export function createPluginDatabaseConnector(
32
+ managedRuntime: SdkManagedRuntime,
33
+ pluginRuntime: Record<string, LotaPlugin>,
34
+ connectedPluginDatabases: Set<string>,
35
+ ): () => Promise<void> {
36
+ return () =>
37
+ managedRuntime.runPromise(
38
+ Effect.gen(function* () {
39
+ for (const [pluginName, plugin] of Object.entries(pluginRuntime)) {
40
+ if (connectedPluginDatabases.has(pluginName)) {
41
+ continue
42
+ }
43
+
44
+ const services = getPluginLifecycleServices(plugin)
45
+ const connectDatabase = Reflect.get(services, 'connectDatabase')
46
+ if (typeof connectDatabase !== 'function') {
47
+ continue
48
+ }
49
+
50
+ const connectDatabaseFn = connectDatabase as (this: typeof services) => Promise<void>
51
+ yield* effectTryPromise(() => connectDatabaseFn.call(services))
52
+ connectedPluginDatabases.add(pluginName)
53
+ }
54
+ }),
55
+ )
56
+ }
57
+
58
+ /**
59
+ * Build a plugin database disconnector. Clears `connectedPluginDatabases`
60
+ * entries regardless of whether the plugin provides a `disconnectDatabase`
61
+ * hook, so the set stays in sync with actual connection state.
62
+ */
63
+ export function createPluginDatabaseDisconnector(
64
+ managedRuntime: SdkManagedRuntime,
65
+ pluginRuntime: Record<string, LotaPlugin>,
66
+ connectedPluginDatabases: Set<string>,
67
+ ): () => Promise<void> {
68
+ return () =>
69
+ managedRuntime.runPromise(
70
+ Effect.gen(function* () {
71
+ for (const [pluginName, plugin] of Object.entries(pluginRuntime)) {
72
+ if (!connectedPluginDatabases.has(pluginName)) {
73
+ continue
74
+ }
75
+
76
+ const services = getPluginLifecycleServices(plugin)
77
+ const disconnectDatabase = Reflect.get(services, 'disconnectDatabase')
78
+ if (typeof disconnectDatabase !== 'function') {
79
+ connectedPluginDatabases.delete(pluginName)
80
+ continue
81
+ }
82
+
83
+ const disconnectDatabaseFn = disconnectDatabase as (this: typeof services) => Promise<void>
84
+ yield* effectTryPromise(() => disconnectDatabaseFn.call(services))
85
+ connectedPluginDatabases.delete(pluginName)
86
+ }
87
+ }),
88
+ )
89
+ }
90
+
91
+ interface CreateDisconnectInput {
92
+ managedRuntime: SdkManagedRuntime
93
+ runPromiseWithCurrentContext: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
94
+ socialChatShutdown: () => Promise<void>
95
+ disconnectPluginDatabases: () => Promise<void>
96
+ onFinalize: () => void
97
+ }
98
+
99
+ /**
100
+ * Compose the runtime `disconnect()` function. The returned function is
101
+ * idempotent: the first call starts the shutdown sequence, subsequent calls
102
+ * return the same in-flight promise.
103
+ */
104
+ export function createRuntimeDisconnect(input: CreateDisconnectInput): () => Promise<void> {
105
+ const { managedRuntime, runPromiseWithCurrentContext, socialChatShutdown, disconnectPluginDatabases, onFinalize } =
106
+ input
107
+
108
+ let disconnectPromise: Promise<void> | null = null
109
+
110
+ return () => {
111
+ if (disconnectPromise) {
112
+ return disconnectPromise
113
+ }
114
+
115
+ disconnectPromise = runPromiseWithCurrentContext(
116
+ Effect.gen(function* () {
117
+ yield* Effect.ignore(effectTryPromise(() => socialChatShutdown()))
118
+ yield* Effect.ignore(effectTryPromise(() => disconnectPluginDatabases()))
119
+ yield* effectTryPromise(() => managedRuntime.dispose())
120
+ }).pipe(Effect.ensuring(Effect.sync(onFinalize))),
121
+ )
122
+ return disconnectPromise
123
+ }
124
+ }