@lota-sdk/core 0.4.9 → 0.4.11

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 (182) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +164 -82
  4. package/src/ai-gateway/index.ts +16 -1
  5. package/src/config/agent-defaults.ts +4 -107
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/config/background-processing.ts +1 -1
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +22 -25
  10. package/src/config/thread-defaults.ts +1 -10
  11. package/src/create-runtime.ts +145 -670
  12. package/src/db/base.service.ts +30 -38
  13. package/src/db/memory-query-builder.ts +2 -1
  14. package/src/db/memory-store.ts +29 -20
  15. package/src/db/memory.ts +188 -195
  16. package/src/db/service-normalization.ts +97 -64
  17. package/src/db/service.ts +496 -384
  18. package/src/db/startup.ts +30 -19
  19. package/src/effect/helpers.ts +30 -5
  20. package/src/effect/index.ts +7 -7
  21. package/src/effect/layers.ts +75 -72
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -71
  24. package/src/index.ts +13 -12
  25. package/src/queues/autonomous-job.queue.ts +177 -143
  26. package/src/queues/context-compaction.queue.ts +41 -39
  27. package/src/queues/delayed-node-promotion.queue.ts +61 -42
  28. package/src/queues/document-processor.queue.ts +5 -3
  29. package/src/queues/index.ts +1 -0
  30. package/src/queues/memory-consolidation.queue.ts +79 -53
  31. package/src/queues/organization-learning.queue.ts +70 -33
  32. package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
  33. package/src/queues/plan-scheduler.queue.ts +101 -97
  34. package/src/queues/post-chat-memory.queue.ts +56 -46
  35. package/src/queues/queue-factory.ts +146 -69
  36. package/src/queues/queues.service.ts +61 -0
  37. package/src/queues/title-generation.queue.ts +44 -44
  38. package/src/redis/connection.ts +181 -164
  39. package/src/redis/org-memory-lock.ts +24 -9
  40. package/src/redis/redis-lease-lock.ts +8 -1
  41. package/src/redis/stream-context.ts +17 -9
  42. package/src/runtime/agent-identity-overrides.ts +7 -3
  43. package/src/runtime/agent-runtime-policy.ts +10 -5
  44. package/src/runtime/agent-stream-helpers.ts +24 -15
  45. package/src/runtime/chat-run-orchestration.ts +1 -1
  46. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  47. package/src/runtime/context-compaction/context-compaction.ts +131 -85
  48. package/src/runtime/domain-layer.ts +203 -0
  49. package/src/runtime/execution-plan-visibility.ts +5 -2
  50. package/src/runtime/graph-designer.ts +0 -14
  51. package/src/runtime/helper-model.ts +8 -4
  52. package/src/runtime/index.ts +1 -1
  53. package/src/runtime/indexed-repositories-policy.ts +2 -6
  54. package/src/runtime/memory/memory-block.ts +19 -9
  55. package/src/runtime/memory/memory-pipeline.ts +53 -66
  56. package/src/runtime/memory/memory-scope.ts +33 -29
  57. package/src/runtime/plugin-resolution.ts +58 -62
  58. package/src/runtime/post-turn-side-effects.ts +139 -161
  59. package/src/runtime/retrieval-adapters.ts +4 -4
  60. package/src/runtime/runtime-config.ts +3 -9
  61. package/src/runtime/runtime-extensions.ts +0 -43
  62. package/src/runtime/runtime-lifecycle.ts +124 -0
  63. package/src/runtime/runtime-services.ts +455 -0
  64. package/src/runtime/runtime-worker-registry.ts +113 -30
  65. package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
  66. package/src/runtime/social-chat/social-chat-history.ts +24 -13
  67. package/src/runtime/social-chat/social-chat.ts +420 -369
  68. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
  69. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  70. package/src/runtime/thread-chat-helpers.ts +18 -9
  71. package/src/runtime/thread-turn-context.ts +28 -74
  72. package/src/runtime/turn-lifecycle.ts +6 -14
  73. package/src/services/agent-activity.service.ts +169 -176
  74. package/src/services/agent-executor.service.ts +207 -196
  75. package/src/services/artifact.service.ts +10 -5
  76. package/src/services/attachment.service.ts +16 -48
  77. package/src/services/autonomous-job.service.ts +81 -87
  78. package/src/services/background-work.service.ts +54 -0
  79. package/src/services/chat-run-registry.service.ts +3 -1
  80. package/src/services/context-compaction.service.ts +8 -10
  81. package/src/services/document-chunk.service.ts +8 -17
  82. package/src/services/execution-plan/execution-plan-graph.ts +122 -109
  83. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  84. package/src/services/execution-plan/execution-plan.service.ts +68 -51
  85. package/src/services/feedback-loop.service.ts +1 -1
  86. package/src/services/global-orchestrator.service.ts +49 -15
  87. package/src/services/graph-full-routing.ts +49 -37
  88. package/src/services/index.ts +1 -0
  89. package/src/services/institutional-memory.service.ts +8 -17
  90. package/src/services/learned-skill.service.ts +38 -35
  91. package/src/services/memory/memory-conversation.ts +10 -5
  92. package/src/services/memory/memory-errors.ts +27 -0
  93. package/src/services/memory/memory-org-memory.ts +14 -3
  94. package/src/services/memory/memory-preseeded.ts +10 -4
  95. package/src/services/memory/memory-utils.ts +2 -1
  96. package/src/services/memory/memory.service.ts +37 -52
  97. package/src/services/memory/rerank.service.ts +3 -11
  98. package/src/services/monitoring-window.service.ts +1 -1
  99. package/src/services/mutating-approval.service.ts +1 -1
  100. package/src/services/node-workspace.service.ts +2 -2
  101. package/src/services/notification.service.ts +16 -4
  102. package/src/services/organization-member.service.ts +1 -1
  103. package/src/services/organization.service.ts +34 -51
  104. package/src/services/ownership-dispatcher.service.ts +148 -95
  105. package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
  106. package/src/services/plan/plan-agent-query.service.ts +13 -9
  107. package/src/services/plan/plan-approval.service.ts +52 -48
  108. package/src/services/plan/plan-artifact.service.ts +2 -2
  109. package/src/services/plan/plan-builder.service.ts +2 -2
  110. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  111. package/src/services/plan/plan-compiler.service.ts +1 -1
  112. package/src/services/plan/plan-completion-side-effects.ts +99 -113
  113. package/src/services/plan/plan-coordination.service.ts +1 -1
  114. package/src/services/plan/plan-cycle.service.ts +171 -202
  115. package/src/services/plan/plan-deadline.service.ts +304 -307
  116. package/src/services/plan/plan-event-delivery.service.ts +84 -72
  117. package/src/services/plan/plan-executor-context.ts +2 -0
  118. package/src/services/plan/plan-executor-graph.ts +375 -353
  119. package/src/services/plan/plan-executor-helpers.ts +60 -75
  120. package/src/services/plan/plan-executor.service.ts +494 -489
  121. package/src/services/plan/plan-run.service.ts +12 -19
  122. package/src/services/plan/plan-scheduler.service.ts +89 -82
  123. package/src/services/plan/plan-template.service.ts +1 -1
  124. package/src/services/plan/plan-transaction-events.ts +8 -5
  125. package/src/services/plan/plan-validator.service.ts +1 -1
  126. package/src/services/plan/plan-workspace.service.ts +17 -11
  127. package/src/services/plugin-executor.service.ts +26 -21
  128. package/src/services/quality-metrics.service.ts +1 -1
  129. package/src/services/queue-job.service.ts +8 -17
  130. package/src/services/recent-activity-title.service.ts +22 -10
  131. package/src/services/recent-activity.service.ts +1 -1
  132. package/src/services/skill-resolver.service.ts +1 -1
  133. package/src/services/social-chat-history.service.ts +37 -20
  134. package/src/services/system-executor.service.ts +25 -20
  135. package/src/services/thread/thread-bootstrap.ts +37 -19
  136. package/src/services/thread/thread-listing.ts +2 -1
  137. package/src/services/thread/thread-memory-block.ts +18 -5
  138. package/src/services/thread/thread-message.service.ts +30 -13
  139. package/src/services/thread/thread-title.service.ts +1 -1
  140. package/src/services/thread/thread-turn-execution.ts +87 -83
  141. package/src/services/thread/thread-turn-preparation.service.ts +65 -40
  142. package/src/services/thread/thread-turn-streaming.ts +32 -36
  143. package/src/services/thread/thread-turn.ts +43 -29
  144. package/src/services/thread/thread.service.ts +32 -8
  145. package/src/services/user.service.ts +1 -1
  146. package/src/services/write-intent-validator.service.ts +1 -1
  147. package/src/storage/attachment-storage.service.ts +7 -4
  148. package/src/storage/generated-document-storage.service.ts +1 -1
  149. package/src/system-agents/context-compaction.agent.ts +1 -1
  150. package/src/system-agents/helper-agent-options.ts +1 -1
  151. package/src/system-agents/memory-reranker.agent.ts +1 -1
  152. package/src/system-agents/memory.agent.ts +1 -1
  153. package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
  154. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  155. package/src/system-agents/skill-extractor.agent.ts +1 -1
  156. package/src/system-agents/skill-manager.agent.ts +1 -1
  157. package/src/system-agents/thread-router.agent.ts +23 -20
  158. package/src/system-agents/title-generator.agent.ts +1 -1
  159. package/src/tools/execution-plan.tool.ts +36 -20
  160. package/src/tools/fetch-webpage.tool.ts +30 -22
  161. package/src/tools/firecrawl-client.ts +1 -6
  162. package/src/tools/plan-approval.tool.ts +9 -1
  163. package/src/tools/remember-memory.tool.ts +3 -6
  164. package/src/tools/research-topic.tool.ts +12 -3
  165. package/src/tools/search-web.tool.ts +26 -18
  166. package/src/tools/search.tool.ts +4 -5
  167. package/src/tools/team-think.tool.ts +139 -121
  168. package/src/utils/async.ts +15 -6
  169. package/src/utils/errors.ts +27 -15
  170. package/src/workers/bootstrap.ts +34 -58
  171. package/src/workers/memory-consolidation.worker.ts +4 -1
  172. package/src/workers/organization-learning.worker.ts +16 -3
  173. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  174. package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
  175. package/src/workers/skill-extraction.runner.ts +13 -15
  176. package/src/workers/worker-utils.ts +14 -8
  177. package/src/config/search.ts +0 -3
  178. package/src/effect/awaitable-effect.ts +0 -87
  179. package/src/effect/runtime-ref.ts +0 -25
  180. package/src/effect/runtime.ts +0 -31
  181. package/src/redis/runtime-connection.ts +0 -10
  182. package/src/runtime/agent-types.ts +0 -1
package/src/db/startup.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { recordIdSchema } from '@lota-sdk/shared'
2
- import { Duration, Effect, Schedule } from 'effect'
2
+ import { Duration, Effect, Match, Schedule } from 'effect'
3
3
  import { BoundQuery, RecordId } from 'surrealdb'
4
4
  import { z } from 'zod'
5
5
 
@@ -146,35 +146,46 @@ function waitForDatabaseBootstrapEffectInternal(params: {
146
146
  }),
147
147
  )
148
148
 
149
+ const logFailure = Match.type<BootstrapWaitFailure>().pipe(
150
+ Match.tag('schema-not-ready', (failure) => {
151
+ params.logger?.info?.(
152
+ `Waiting for ${params.label} schema readiness (${attempt}, expected=${expectedFingerprint}, current=${failure.currentFingerprint})`,
153
+ )
154
+ }),
155
+ Match.tag('read-failed', (failure) => {
156
+ params.logger?.warn?.(
157
+ `Waiting for ${params.label} schema readiness (${attempt}): ${getErrorMessage(failure.cause)}`,
158
+ )
159
+ }),
160
+ Match.exhaustive,
161
+ )
162
+
163
+ const toTimeoutError = Match.type<BootstrapWaitFailure>().pipe(
164
+ Match.tag(
165
+ 'schema-not-ready',
166
+ () => new DatabaseError({ message: `Timed out waiting for ${params.label} schema readiness` }),
167
+ ),
168
+ Match.tag(
169
+ 'read-failed',
170
+ (failure) =>
171
+ new DatabaseError({ message: `Timed out waiting for ${params.label} schema readiness`, cause: failure.cause }),
172
+ ),
173
+ Match.exhaustive,
174
+ )
175
+
149
176
  return waitForExpectedFingerprint.pipe(
150
177
  Effect.tapError((failure) =>
151
178
  Effect.sync(() => {
152
179
  attempt++
153
180
  if (!shouldLogRetry(attempt)) return
154
-
155
- if (failure._tag === 'schema-not-ready') {
156
- params.logger?.info?.(
157
- `Waiting for ${params.label} schema readiness (${attempt}, expected=${expectedFingerprint}, current=${failure.currentFingerprint})`,
158
- )
159
- return
160
- }
161
-
162
- params.logger?.warn?.(
163
- `Waiting for ${params.label} schema readiness (${attempt}): ${getErrorMessage(failure.cause)}`,
164
- )
181
+ logFailure(failure)
165
182
  }),
166
183
  ),
167
184
  Effect.retry({
168
185
  times: Math.max(0, Math.ceil(maxWaitMs / retryDelayMs) - 1),
169
186
  schedule: Schedule.fixed(Duration.millis(retryDelayMs)),
170
187
  }),
171
- Effect.mapError(
172
- (failure) =>
173
- new DatabaseError({
174
- message: `Timed out waiting for ${params.label} schema readiness`,
175
- ...(failure._tag === 'read-failed' ? { cause: failure.cause } : {}),
176
- }),
177
- ),
188
+ Effect.mapError(toTimeoutError),
178
189
  )
179
190
  }
180
191
 
@@ -18,6 +18,12 @@ type PromiseEffectEvaluator<A, E = never, R = never> =
18
18
  | (() => PromiseLike<A> | Effect.Effect<A, E, R>)
19
19
  | ((signal: AbortSignal) => PromiseLike<A> | Effect.Effect<A, E, R>)
20
20
 
21
+ function invokePromiseEffectEvaluator<A, E, R>(
22
+ evaluate: PromiseEffectEvaluator<A, E, R>,
23
+ ): PromiseLike<A> | Effect.Effect<A, E, R> {
24
+ return (evaluate as () => PromiseLike<A> | Effect.Effect<A, E, R>)()
25
+ }
26
+
21
27
  export function effectTryPromise<A, E = never, R = never>(
22
28
  evaluate: PromiseEffectEvaluator<A, E, R>,
23
29
  ): Effect.Effect<A, Cause.UnknownError, R>
@@ -31,7 +37,7 @@ export function effectTryPromise<A, E1, E2, R>(
31
37
  ): Effect.Effect<A, E2 | Cause.UnknownError, R> {
32
38
  return Effect.suspend(() => {
33
39
  try {
34
- const value = (evaluate as () => PromiseLike<A> | Effect.Effect<A, E1, R>)()
40
+ const value = invokePromiseEffectEvaluator(evaluate)
35
41
  if (Effect.isEffect(value)) {
36
42
  if (onError) {
37
43
  return value.pipe(Effect.mapError((cause) => onError(cause)))
@@ -49,13 +55,13 @@ export function effectTryPromise<A, E1, E2, R>(
49
55
  })
50
56
  }
51
57
 
52
- export function effectTryServicePromise<A>(
53
- evaluate: PromiseEffectEvaluator<A, unknown, never>,
58
+ export function effectTryServicePromise<A, E = never, R = never>(
59
+ evaluate: PromiseEffectEvaluator<A, E, R>,
54
60
  message: string,
55
- ): Effect.Effect<A, ServiceError, never> {
61
+ ): Effect.Effect<A, ServiceError, R> {
56
62
  return Effect.suspend(() => {
57
63
  try {
58
- const value = (evaluate as () => PromiseLike<A> | Effect.Effect<A, unknown, never>)()
64
+ const value = invokePromiseEffectEvaluator(evaluate)
59
65
  if (Effect.isEffect(value)) {
60
66
  return value.pipe(Effect.mapError((cause) => new ServiceError({ message, cause })))
61
67
  }
@@ -74,6 +80,16 @@ export function makeEffectTryPromiseWithMessage<E>(onError: (message: string, ca
74
80
  ): Effect.Effect<A, E, R> => effectTryPromise(evaluate, (cause) => onError(message, cause))
75
81
  }
76
82
 
83
+ export function makeEffectTryPromiseWithOperation<E>(
84
+ onError: (operation: string, message: string, cause: unknown) => E,
85
+ ) {
86
+ return <A, E1 = never, R = never>(
87
+ operation: string,
88
+ message: string,
89
+ evaluate: PromiseEffectEvaluator<A, E1, R>,
90
+ ): Effect.Effect<A, E, R> => effectTryPromise(evaluate, (cause) => onError(operation, message, cause))
91
+ }
92
+
77
93
  export function effectTryMaybeAsync<A>(evaluate: () => A | PromiseLike<A>): Effect.Effect<A, Cause.UnknownError, never>
78
94
  export function effectTryMaybeAsync<A, E>(
79
95
  evaluate: () => A | PromiseLike<A>,
@@ -96,3 +112,12 @@ export function effectTryMaybeAsync<A, E>(
96
112
  }
97
113
  })
98
114
  }
115
+
116
+ export function iterateEffect<A, E, R>(
117
+ initial: A,
118
+ options: { while: (state: A) => boolean; body: (state: A) => Effect.Effect<A, E, R> },
119
+ ): Effect.Effect<A, E, R> {
120
+ const step = (state: A): Effect.Effect<A, E, R> =>
121
+ options.while(state) ? options.body(state).pipe(Effect.flatMap(step)) : Effect.succeed(state)
122
+ return Effect.suspend(() => step(initial))
123
+ }
@@ -1,22 +1,22 @@
1
- export * from './awaitable-effect'
2
1
  export * from './helpers'
3
2
  export * from './layers'
4
- export * from './runtime'
5
3
  export * from './services'
6
4
  export * from './zod'
7
5
  export {
8
6
  ActiveThreadRunConflictError,
7
+ AiGenerationError,
8
+ BaseServicePersistenceError,
9
9
  ConfigurationError,
10
10
  ConflictError,
11
11
  DatabaseError,
12
+ ForbiddenError,
12
13
  LockAcquisitionError,
13
14
  LockLostError,
14
15
  RedisError,
15
- isEffectError,
16
- ThreadTurnError as EffectThreadTurnError,
16
+ ServiceError,
17
+ ThreadTurnError,
17
18
  TimeoutError,
18
- ValidationError as EffectValidationError,
19
- BadRequestError as EffectBadRequestError,
20
- NotFoundError as EffectNotFoundError,
19
+ ValidationError,
20
+ isEffectError,
21
21
  } from './errors'
22
22
  export type { EffectError, ValidationIssue } from './errors'
@@ -1,10 +1,13 @@
1
1
  import type { ToolSet } from 'ai'
2
2
  import { Effect, Layer, Logger, References } from 'effect'
3
+ import { DevTools } from 'effect/unstable/devtools'
4
+ import { FetchHttpClient } from 'effect/unstable/http'
5
+ import { Otlp } from 'effect/unstable/observability'
3
6
 
4
7
  import type { CoreThreadProfile } from '../config/agent-defaults'
5
8
  import { resolveAgentConfig, resolveAgentFactoryConfig } from '../config/agent-defaults'
6
9
  import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
7
- import { configureLotaLogger, getLotaLoggers, toEffectLogLevel } from '../config/logger'
10
+ import { getLotaLoggers, toEffectLogLevel } from '../config/logger'
8
11
  import type { LotaLogLevel } from '../config/logger'
9
12
  import { resolveThreadConfig } from '../config/thread-defaults'
10
13
  import type { LotaThreadConfig } from '../config/thread-defaults'
@@ -12,14 +15,13 @@ import { LOTA_SDK_DATABASE_NAME } from '../db/sdk-database'
12
15
  import type { SurrealDatabaseConfig } from '../db/service'
13
16
  import { SurrealDBService } from '../db/service'
14
17
  import type { CreateRedisConnectionManagerOptions } from '../redis/connection'
15
- import { createRedisConnectionManager } from '../redis/connection'
18
+ import { makeRedisConnectionManager } from '../redis/connection'
16
19
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
17
20
  import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from '../runtime/runtime-extensions'
18
21
  import type { LotaRuntimeWorkerExtensions } from '../runtime/runtime-worker-registry'
19
22
  import { QueueJobServiceLive } from '../services/queue-job.service'
20
23
  import { getErrorMessage } from '../utils/errors'
21
- import { DatabaseError, RedisError } from './errors'
22
- import { effectTryServicePromise } from './helpers'
24
+ import { DatabaseError } from './errors'
23
25
  import {
24
26
  AppLoggerTag,
25
27
  AgentConfigServiceTag,
@@ -61,35 +63,15 @@ export function DatabaseLive(config: SurrealDatabaseConfig) {
61
63
  }
62
64
 
63
65
  export function RedisLive(config: CreateRedisConnectionManagerOptions) {
64
- return Layer.effect(
65
- RedisServiceTag,
66
- Effect.acquireRelease(
67
- Effect.try({
68
- try: () => createRedisConnectionManager(config),
69
- catch: (error) =>
70
- new RedisError({
71
- message: `Failed to create Redis connection manager: ${getErrorMessage(error)}`,
72
- cause: error,
73
- }),
74
- }),
75
- (redisManager) =>
76
- Effect.ignore(
77
- effectTryServicePromise(
78
- () => redisManager.closeConnection(),
79
- 'Failed to close Redis connection manager',
80
- ).pipe(
81
- Effect.tapError((error) =>
82
- Effect.logWarning(`Failed to clean up Redis connection manager: ${getErrorMessage(error)}`),
83
- ),
84
- ),
85
- ),
86
- ),
87
- )
66
+ return Layer.effect(RedisServiceTag, makeRedisConnectionManager(config))
88
67
  }
89
68
 
90
69
  export function AppLoggerLive(level: LotaLogLevel = 'info') {
70
+ // The logger level used by `serverLogger`/`chatLogger`/`aiLogger` is read
71
+ // once from `LOG_LEVEL` at module load. This layer wires the Effect-side
72
+ // console logger and minimum level plus the `AppLoggerTag` for callers
73
+ // that want the logger set from context.
91
74
  return Layer.mergeAll(
92
- Layer.effectDiscard(Effect.sync(() => configureLotaLogger(level))),
93
75
  Layer.succeed(AppLoggerTag, getLotaLoggers()),
94
76
  Logger.layer([Logger.consolePretty()]),
95
77
  Layer.succeed(References.MinimumLogLevel, toEffectLogLevel(level)),
@@ -143,12 +125,17 @@ export function RuntimeExtensionsLive(params: {
143
125
  // ── Shared Infrastructure Layer Builders ─────────────────────────────
144
126
 
145
127
  /**
146
- * Builds the full infrastructure layer used by `createLotaRuntime`.
147
- * Contains all config, database, redis, agent, thread, and extension layers.
128
+ * Common config-to-Layer conversions shared by the runtime and sandboxed
129
+ * worker infrastructure builders. Covers logging, runtime config, agents,
130
+ * threads, database, and redis. Agent display names are overridable for
131
+ * the socialChat agent injection flow.
148
132
  */
149
- export function buildInfrastructureLayer(
133
+ function buildBaseConfigLayers(
150
134
  runtimeConfig: ResolvedLotaRuntimeConfig,
151
- options: { resolvedAgentDisplayNames: Record<string, string>; observabilityLayer: Layer.Layer<never> },
135
+ options: {
136
+ resolvedAgentDisplayNames?: Record<string, string>
137
+ runtimeExtensions: Parameters<typeof RuntimeExtensionsLive>[0]
138
+ },
152
139
  ) {
153
140
  return Layer.mergeAll(
154
141
  AppLoggerLive(runtimeConfig.logging.level),
@@ -156,7 +143,7 @@ export function buildInfrastructureLayer(
156
143
  AgentConfigLive({
157
144
  roster: runtimeConfig.agents.roster,
158
145
  leadAgentId: runtimeConfig.agents.leadAgentId,
159
- displayNames: options.resolvedAgentDisplayNames,
146
+ displayNames: options.resolvedAgentDisplayNames ?? runtimeConfig.agents.displayNames,
160
147
  shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
161
148
  descriptions: runtimeConfig.agents.descriptions,
162
149
  routerModelId: runtimeConfig.agents.routerModelId,
@@ -170,12 +157,7 @@ export function buildInfrastructureLayer(
170
157
  pluginRuntime: runtimeConfig.pluginRuntime,
171
158
  }),
172
159
  ThreadConfigLive({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads }),
173
- RuntimeExtensionsLive({
174
- adapters: runtimeConfig.runtimeAdapters,
175
- turnHooks: runtimeConfig.turnHooks,
176
- toolProviders: runtimeConfig.toolProviders ?? {},
177
- extraWorkers: runtimeConfig.extraWorkers,
178
- }),
160
+ RuntimeExtensionsLive(options.runtimeExtensions),
179
161
  DatabaseLive({
180
162
  url: runtimeConfig.database.url,
181
163
  namespace: runtimeConfig.database.namespace,
@@ -184,45 +166,66 @@ export function buildInfrastructureLayer(
184
166
  password: runtimeConfig.database.password,
185
167
  }),
186
168
  RedisLive({ url: runtimeConfig.redis.url }),
187
- options.observabilityLayer,
188
169
  )
189
170
  }
190
171
 
191
172
  /**
192
- * Builds the minimal infrastructure layer used by sandboxed workers.
193
- * Omits observability and uses empty runtime extensions.
173
+ * Builds the observability layer (OTLP + DevTools) from a runtime config.
174
+ * Returns an empty layer when no observability endpoints are configured.
194
175
  */
195
- export function buildWorkerInfrastructureLayer(runtimeConfig: ResolvedLotaRuntimeConfig) {
196
- const infrastructureLayer = Layer.mergeAll(
197
- AppLoggerLive(runtimeConfig.logging.level),
198
- RuntimeConfigLive(runtimeConfig),
199
- AgentConfigLive({
200
- roster: runtimeConfig.agents.roster,
201
- leadAgentId: runtimeConfig.agents.leadAgentId,
202
- displayNames: runtimeConfig.agents.displayNames,
203
- shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
204
- descriptions: runtimeConfig.agents.descriptions,
205
- routerModelId: runtimeConfig.agents.routerModelId,
206
- teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
207
- getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
208
- }),
209
- AgentFactoryLive({
210
- createAgent: runtimeConfig.agents.createAgent,
211
- buildAgentTools: runtimeConfig.agents.buildAgentTools,
212
- getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
213
- pluginRuntime: runtimeConfig.pluginRuntime,
214
- }),
215
- ThreadConfigLive({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads }),
216
- RuntimeExtensionsLive({}),
217
- DatabaseLive({
218
- url: runtimeConfig.database.url,
219
- namespace: runtimeConfig.database.namespace,
220
- database: LOTA_SDK_DATABASE_NAME,
221
- username: runtimeConfig.database.username,
222
- password: runtimeConfig.database.password,
176
+ function buildObservabilityLayer(runtimeConfig: ResolvedLotaRuntimeConfig): Layer.Layer<never> {
177
+ let layer: Layer.Layer<never> = Layer.empty
178
+ const observability = runtimeConfig.observability
179
+
180
+ if (observability.otlpBaseUrl) {
181
+ layer = Layer.merge(
182
+ layer,
183
+ Otlp.layerJson({
184
+ baseUrl: observability.otlpBaseUrl,
185
+ resource: {
186
+ serviceName: observability.serviceName,
187
+ ...(observability.serviceVersion ? { serviceVersion: observability.serviceVersion } : {}),
188
+ },
189
+ ...(observability.otlpHeaders ? { headers: observability.otlpHeaders } : {}),
190
+ }).pipe(Layer.provide(FetchHttpClient.layer)),
191
+ )
192
+ }
193
+
194
+ if (observability.devToolsUrl && Bun.env.NODE_ENV !== 'production') {
195
+ layer = Layer.merge(layer, DevTools.layer(observability.devToolsUrl))
196
+ }
197
+
198
+ return layer
199
+ }
200
+
201
+ /**
202
+ * Builds the full infrastructure layer used by `createLotaRuntime`. Combines
203
+ * all config, database, redis, agent, thread, and extension layers with the
204
+ * observability layer computed from the runtime config.
205
+ */
206
+ export function buildInfrastructureLayer(
207
+ runtimeConfig: ResolvedLotaRuntimeConfig,
208
+ options: { resolvedAgentDisplayNames: Record<string, string> },
209
+ ) {
210
+ return Layer.mergeAll(
211
+ buildBaseConfigLayers(runtimeConfig, {
212
+ resolvedAgentDisplayNames: options.resolvedAgentDisplayNames,
213
+ runtimeExtensions: {
214
+ adapters: runtimeConfig.runtimeAdapters,
215
+ turnHooks: runtimeConfig.turnHooks,
216
+ toolProviders: runtimeConfig.toolProviders ?? {},
217
+ extraWorkers: runtimeConfig.extraWorkers,
218
+ },
223
219
  }),
224
- RedisLive({ url: runtimeConfig.redis.url }),
220
+ buildObservabilityLayer(runtimeConfig),
225
221
  )
222
+ }
226
223
 
224
+ /**
225
+ * Builds the minimal infrastructure layer used by sandboxed workers.
226
+ * Omits observability and uses empty runtime extensions.
227
+ */
228
+ export function buildWorkerInfrastructureLayer(runtimeConfig: ResolvedLotaRuntimeConfig) {
229
+ const infrastructureLayer = buildBaseConfigLayers(runtimeConfig, { runtimeExtensions: {} })
227
230
  return QueueJobServiceLive.pipe(Layer.provideMerge(infrastructureLayer))
228
231
  }
@@ -11,18 +11,22 @@ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
11
11
  import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from '../runtime/runtime-extensions'
12
12
  import type { LotaRuntimeWorkerExtensions } from '../runtime/runtime-worker-registry'
13
13
 
14
- export class DatabaseServiceTag extends Context.Service<DatabaseServiceTag, SurrealDBService>()('DatabaseService') {}
14
+ export class DatabaseServiceTag extends Context.Service<DatabaseServiceTag, SurrealDBService>()(
15
+ '@lota-sdk/core/DatabaseService',
16
+ ) {}
15
17
 
16
- export class RedisServiceTag extends Context.Service<RedisServiceTag, RedisConnectionManager>()('RedisService') {}
18
+ export class RedisServiceTag extends Context.Service<RedisServiceTag, RedisConnectionManager>()(
19
+ '@lota-sdk/core/RedisService',
20
+ ) {}
17
21
 
18
- export class AppLoggerTag extends Context.Service<AppLoggerTag, LotaLoggerSet>()('AppLogger') {}
22
+ export class AppLoggerTag extends Context.Service<AppLoggerTag, LotaLoggerSet>()('@lota-sdk/core/AppLogger') {}
19
23
 
20
24
  export class RuntimeConfigServiceTag extends Context.Service<RuntimeConfigServiceTag, ResolvedLotaRuntimeConfig>()(
21
- 'RuntimeConfigService',
25
+ '@lota-sdk/core/RuntimeConfigService',
22
26
  ) {}
23
27
 
24
28
  export class AgentConfigServiceTag extends Context.Service<AgentConfigServiceTag, ResolvedAgentConfig>()(
25
- 'AgentConfigService',
29
+ '@lota-sdk/core/AgentConfigService',
26
30
  ) {}
27
31
 
28
32
  export class AgentFactoryServiceTag extends Context.Service<
@@ -33,25 +37,25 @@ export class AgentFactoryServiceTag extends Context.Service<
33
37
  readonly getAgentRuntimeConfig: AgentRuntimeConfigProvider
34
38
  readonly pluginRuntime?: Record<string, unknown>
35
39
  }
36
- >()('AgentFactoryService') {}
40
+ >()('@lota-sdk/core/AgentFactoryService') {}
37
41
 
38
42
  export class ThreadConfigServiceTag extends Context.Service<ThreadConfigServiceTag, ResolvedThreadBootstrapConfig>()(
39
- 'ThreadConfigService',
43
+ '@lota-sdk/core/ThreadConfigService',
40
44
  ) {}
41
45
 
42
46
  export class RuntimeAdaptersServiceTag extends Context.Service<RuntimeAdaptersServiceTag, LotaRuntimeAdapters>()(
43
- 'RuntimeAdaptersService',
47
+ '@lota-sdk/core/RuntimeAdaptersService',
44
48
  ) {}
45
49
 
46
50
  export class TurnHooksServiceTag extends Context.Service<TurnHooksServiceTag, LotaRuntimeTurnHooks>()(
47
- 'TurnHooksService',
51
+ '@lota-sdk/core/TurnHooksService',
48
52
  ) {}
49
53
 
50
54
  export class ToolProvidersServiceTag extends Context.Service<ToolProvidersServiceTag, ToolSet>()(
51
- 'ToolProvidersService',
55
+ '@lota-sdk/core/ToolProvidersService',
52
56
  ) {}
53
57
 
54
58
  export class RuntimeWorkerExtensionsServiceTag extends Context.Service<
55
59
  RuntimeWorkerExtensionsServiceTag,
56
60
  LotaRuntimeWorkerExtensions
57
- >()('RuntimeWorkerExtensionsService') {}
61
+ >()('@lota-sdk/core/RuntimeWorkerExtensionsService') {}
@@ -2,7 +2,6 @@ import { embed, embedMany } from 'ai'
2
2
  import { Schema, Effect } from 'effect'
3
3
 
4
4
  import { ConfigurationError } from '../effect/errors'
5
- import { getOptionalCurrentRuntime } from '../effect/runtime-ref'
6
5
  import { getDirectOpenRouterProvider, normalizeDirectOpenRouterModelId } from '../openrouter/direct-provider'
7
6
 
8
7
  const SUPPORTED_EMBEDDING_PREFIXES = ['openai/', 'openrouter/'] as const
@@ -92,11 +91,6 @@ export class ProviderEmbeddings {
92
91
  return redisCache.get(this.getModelId(), text)
93
92
  }
94
93
 
95
- private runEffect<A>(effect: Effect.Effect<A, EmbeddingProviderError>): Promise<A> {
96
- const runtime = getOptionalCurrentRuntime()
97
- return runtime ? runtime.runPromise(effect) : Effect.runPromise(effect)
98
- }
99
-
100
94
  embedQuery(text: string): Promise<number[]> {
101
95
  const input = text.trim()
102
96
  if (!input) return Promise.resolve([])
@@ -105,7 +99,7 @@ export class ProviderEmbeddings {
105
99
  const pending = this.inflightEmbeddings.get(dedupKey)
106
100
  if (pending) return pending
107
101
 
108
- const promise = this.runEffect(this.executeEmbedQueryEffect(input))
102
+ const promise = Effect.runPromise(this.executeEmbedQueryEffect(input))
109
103
  this.inflightEmbeddings.set(dedupKey, promise)
110
104
  void promise.finally(() => this.inflightEmbeddings.delete(dedupKey))
111
105
 
@@ -113,28 +107,28 @@ export class ProviderEmbeddings {
113
107
  }
114
108
 
115
109
  private executeEmbedQueryEffect(input: string): Effect.Effect<number[], EmbeddingProviderError> {
116
- const self = this
117
-
118
- return Effect.gen(function* () {
119
- const cached = yield* tryEmbeddingPromise('Failed to load cached query embedding.', () =>
120
- self.loadCachedEmbedding(input),
121
- )
122
- if (cached) {
123
- return cached
124
- }
125
-
126
- const result = yield* tryEmbeddingPromise('Failed to generate query embedding.', () =>
127
- self.embedFn({ model: self.getModel(), value: input, maxRetries: 2 }),
128
- )
129
- const embedding = normalizeEmbedding(result.embedding)
130
-
131
- const redisCache = self.getCache()
132
- if (redisCache) {
133
- void redisCache.set(self.getModelId(), input, embedding)
134
- }
135
-
136
- return embedding
137
- }).pipe(Effect.withSpan('ProviderEmbeddings.executeEmbedQuery'))
110
+ return Effect.gen(
111
+ function* (this: ProviderEmbeddings) {
112
+ const cached = yield* tryEmbeddingPromise('Failed to load cached query embedding.', () =>
113
+ this.loadCachedEmbedding(input),
114
+ )
115
+ if (cached) {
116
+ return cached
117
+ }
118
+
119
+ const result = yield* tryEmbeddingPromise('Failed to generate query embedding.', () =>
120
+ this.embedFn({ model: this.getModel(), value: input, maxRetries: 2 }),
121
+ )
122
+ const embedding = normalizeEmbedding(result.embedding)
123
+
124
+ const redisCache = this.getCache()
125
+ if (redisCache) {
126
+ void redisCache.set(this.getModelId(), input, embedding)
127
+ }
128
+
129
+ return embedding
130
+ }.bind(this),
131
+ ).pipe(Effect.withSpan('ProviderEmbeddings.executeEmbedQuery'))
138
132
  }
139
133
 
140
134
  embedDocuments(values: string[]): Promise<number[][]> {
@@ -150,58 +144,58 @@ export class ProviderEmbeddings {
150
144
  }
151
145
 
152
146
  const uniqueTexts = [...new Set(nonEmptyEntries.map((entry) => entry.value))]
153
- return this.runEffect(this.embedDocumentsEffect(normalized, uniqueTexts))
147
+ return Effect.runPromise(this.embedDocumentsEffect(normalized, uniqueTexts))
154
148
  }
155
149
 
156
150
  private embedDocumentsEffect(
157
151
  normalized: string[],
158
152
  uniqueTexts: string[],
159
153
  ): Effect.Effect<number[][], EmbeddingProviderError> {
160
- const self = this
161
-
162
- return Effect.gen(function* () {
163
- const embeddingsByText = new Map<string, number[]>()
164
- let missingTexts = [...uniqueTexts]
165
- const redisCache = self.getCache()
166
- const redisResults =
167
- redisCache && missingTexts.length > 0
168
- ? yield* Effect.all(
169
- missingTexts.map((text) =>
170
- tryEmbeddingPromise('Failed to load cached document embedding.', () =>
171
- redisCache.get(self.getModelId(), text),
172
- ).pipe(Effect.map((embedding) => ({ text, embedding }))),
173
- ),
174
- )
175
- : ([] as Array<{ text: string; embedding: number[] | null }>)
176
-
177
- if (redisCache && missingTexts.length > 0) {
178
- missingTexts = []
179
- for (const result of redisResults) {
180
- if (!result.embedding) {
181
- missingTexts.push(result.text)
182
- continue
154
+ return Effect.gen(
155
+ function* (this: ProviderEmbeddings) {
156
+ const embeddingsByText = new Map<string, number[]>()
157
+ let missingTexts = [...uniqueTexts]
158
+ const redisCache = this.getCache()
159
+ const redisResults =
160
+ redisCache && missingTexts.length > 0
161
+ ? yield* Effect.all(
162
+ missingTexts.map((text) =>
163
+ tryEmbeddingPromise('Failed to load cached document embedding.', () =>
164
+ redisCache.get(this.getModelId(), text),
165
+ ).pipe(Effect.map((embedding) => ({ text, embedding }))),
166
+ ),
167
+ )
168
+ : ([] as Array<{ text: string; embedding: number[] | null }>)
169
+
170
+ if (redisCache && missingTexts.length > 0) {
171
+ missingTexts = []
172
+ for (const result of redisResults) {
173
+ if (!result.embedding) {
174
+ missingTexts.push(result.text)
175
+ continue
176
+ }
177
+
178
+ embeddingsByText.set(result.text, result.embedding)
183
179
  }
184
-
185
- embeddingsByText.set(result.text, result.embedding)
186
180
  }
187
- }
188
181
 
189
- if (missingTexts.length === 0) {
190
- return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
191
- }
192
-
193
- const result = yield* tryEmbeddingPromise('Failed to generate document embeddings.', () =>
194
- self.embedManyFn({ model: self.getModel(), values: missingTexts, maxRetries: 2 }),
195
- )
196
- missingTexts.forEach((text, index) => {
197
- const embedding = normalizeEmbedding(result.embeddings[index] ?? [])
198
- embeddingsByText.set(text, embedding)
199
- if (redisCache) {
200
- void redisCache.set(self.getModelId(), text, embedding)
182
+ if (missingTexts.length === 0) {
183
+ return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
201
184
  }
202
- })
203
185
 
204
- return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
205
- }).pipe(Effect.withSpan('ProviderEmbeddings.embedDocuments'))
186
+ const result = yield* tryEmbeddingPromise('Failed to generate document embeddings.', () =>
187
+ this.embedManyFn({ model: this.getModel(), values: missingTexts, maxRetries: 2 }),
188
+ )
189
+ missingTexts.forEach((text, index) => {
190
+ const embedding = normalizeEmbedding(result.embeddings[index] ?? [])
191
+ embeddingsByText.set(text, embedding)
192
+ if (redisCache) {
193
+ void redisCache.set(this.getModelId(), text, embedding)
194
+ }
195
+ })
196
+
197
+ return normalized.map((text) => (text ? (embeddingsByText.get(text) ?? []) : []))
198
+ }.bind(this),
199
+ ).pipe(Effect.withSpan('ProviderEmbeddings.embedDocuments'))
206
200
  }
207
201
  }