@lota-sdk/core 0.4.9 → 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 (158) 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 +38 -10
  4. package/src/config/agent-defaults.ts +22 -9
  5. package/src/config/agent-types.ts +1 -1
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/index.ts +0 -1
  8. package/src/config/logger.ts +20 -7
  9. package/src/config/thread-defaults.ts +12 -4
  10. package/src/create-runtime.ts +69 -656
  11. package/src/db/memory-query-builder.ts +2 -1
  12. package/src/db/memory-store.ts +29 -20
  13. package/src/db/memory.ts +188 -195
  14. package/src/db/service-normalization.ts +97 -64
  15. package/src/db/service.ts +706 -538
  16. package/src/db/startup.ts +30 -19
  17. package/src/effect/awaitable-effect.ts +46 -37
  18. package/src/effect/helpers.ts +30 -5
  19. package/src/effect/index.ts +7 -5
  20. package/src/effect/layers.ts +82 -72
  21. package/src/effect/runtime.ts +18 -3
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -66
  24. package/src/index.ts +13 -11
  25. package/src/queues/autonomous-job.queue.ts +59 -71
  26. package/src/queues/context-compaction.queue.ts +6 -18
  27. package/src/queues/delayed-node-promotion.queue.ts +9 -17
  28. package/src/queues/organization-learning.queue.ts +17 -4
  29. package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
  30. package/src/queues/plan-scheduler.queue.ts +6 -18
  31. package/src/queues/post-chat-memory.queue.ts +6 -18
  32. package/src/queues/queue-factory.ts +128 -50
  33. package/src/queues/title-generation.queue.ts +6 -17
  34. package/src/redis/connection.ts +181 -164
  35. package/src/redis/runtime-connection.ts +13 -3
  36. package/src/redis/stream-context.ts +17 -9
  37. package/src/runtime/agent-runtime-policy.ts +1 -1
  38. package/src/runtime/agent-stream-helpers.ts +15 -11
  39. package/src/runtime/chat-run-orchestration.ts +1 -1
  40. package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
  41. package/src/runtime/context-compaction/context-compaction.ts +126 -82
  42. package/src/runtime/domain-layer.ts +192 -0
  43. package/src/runtime/graph-designer.ts +15 -7
  44. package/src/runtime/helper-model.ts +8 -4
  45. package/src/runtime/index.ts +0 -1
  46. package/src/runtime/memory/memory-block.ts +19 -9
  47. package/src/runtime/memory/memory-pipeline.ts +53 -66
  48. package/src/runtime/memory/memory-scope.ts +33 -29
  49. package/src/runtime/plugin-resolution.ts +33 -54
  50. package/src/runtime/post-turn-side-effects.ts +6 -26
  51. package/src/runtime/retrieval-adapters.ts +4 -4
  52. package/src/runtime/runtime-accessors.ts +92 -0
  53. package/src/runtime/runtime-config.ts +3 -3
  54. package/src/runtime/runtime-extensions.ts +20 -9
  55. package/src/runtime/runtime-lifecycle.ts +124 -0
  56. package/src/runtime/runtime-services.ts +386 -0
  57. package/src/runtime/runtime-token.ts +47 -0
  58. package/src/runtime/social-chat/social-chat-agent-runner.ts +7 -5
  59. package/src/runtime/social-chat/social-chat-history.ts +21 -12
  60. package/src/runtime/social-chat/social-chat.ts +401 -365
  61. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
  62. package/src/runtime/thread-turn-context.ts +21 -27
  63. package/src/services/agent-activity.service.ts +1 -1
  64. package/src/services/agent-executor.service.ts +179 -187
  65. package/src/services/artifact.service.ts +10 -5
  66. package/src/services/attachment.service.ts +35 -1
  67. package/src/services/autonomous-job.service.ts +58 -56
  68. package/src/services/background-work.service.ts +54 -0
  69. package/src/services/chat-run-registry.service.ts +3 -1
  70. package/src/services/context-compaction.service.ts +1 -1
  71. package/src/services/document-chunk.service.ts +8 -17
  72. package/src/services/execution-plan/execution-plan-graph.ts +74 -52
  73. package/src/services/execution-plan/execution-plan.service.ts +1 -1
  74. package/src/services/feedback-loop.service.ts +1 -1
  75. package/src/services/global-orchestrator.service.ts +33 -10
  76. package/src/services/graph-full-routing.ts +44 -33
  77. package/src/services/index.ts +1 -0
  78. package/src/services/institutional-memory.service.ts +8 -17
  79. package/src/services/learned-skill.service.ts +38 -35
  80. package/src/services/memory/memory-errors.ts +27 -0
  81. package/src/services/memory/memory-org-memory.ts +14 -3
  82. package/src/services/memory/memory-preseeded.ts +10 -4
  83. package/src/services/memory/memory-utils.ts +2 -1
  84. package/src/services/memory/memory.service.ts +26 -44
  85. package/src/services/memory/rerank.service.ts +3 -11
  86. package/src/services/monitoring-window.service.ts +1 -1
  87. package/src/services/mutating-approval.service.ts +1 -1
  88. package/src/services/node-workspace.service.ts +2 -2
  89. package/src/services/notification.service.ts +16 -4
  90. package/src/services/organization-member.service.ts +1 -1
  91. package/src/services/organization.service.ts +34 -51
  92. package/src/services/ownership-dispatcher.service.ts +132 -90
  93. package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
  94. package/src/services/plan/plan-agent-query.service.ts +1 -1
  95. package/src/services/plan/plan-approval.service.ts +52 -48
  96. package/src/services/plan/plan-artifact.service.ts +2 -2
  97. package/src/services/plan/plan-builder.service.ts +2 -2
  98. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  99. package/src/services/plan/plan-compiler.service.ts +1 -1
  100. package/src/services/plan/plan-completion-side-effects.ts +18 -24
  101. package/src/services/plan/plan-coordination.service.ts +1 -1
  102. package/src/services/plan/plan-cycle.service.ts +171 -164
  103. package/src/services/plan/plan-deadline.service.ts +290 -304
  104. package/src/services/plan/plan-event-delivery.service.ts +44 -39
  105. package/src/services/plan/plan-executor-graph.ts +114 -67
  106. package/src/services/plan/plan-executor-helpers.ts +60 -75
  107. package/src/services/plan/plan-executor.service.ts +550 -467
  108. package/src/services/plan/plan-run.service.ts +12 -19
  109. package/src/services/plan/plan-scheduler.service.ts +27 -33
  110. package/src/services/plan/plan-template.service.ts +1 -1
  111. package/src/services/plan/plan-transaction-events.ts +8 -5
  112. package/src/services/plan/plan-validator.service.ts +1 -1
  113. package/src/services/plan/plan-workspace.service.ts +17 -11
  114. package/src/services/plugin-executor.service.ts +26 -21
  115. package/src/services/quality-metrics.service.ts +1 -1
  116. package/src/services/queue-job.service.ts +8 -17
  117. package/src/services/recent-activity-title.service.ts +17 -9
  118. package/src/services/recent-activity.service.ts +1 -1
  119. package/src/services/skill-resolver.service.ts +1 -1
  120. package/src/services/social-chat-history.service.ts +37 -20
  121. package/src/services/system-executor.service.ts +25 -20
  122. package/src/services/thread/thread-bootstrap.ts +26 -10
  123. package/src/services/thread/thread-listing.ts +2 -1
  124. package/src/services/thread/thread-memory-block.ts +18 -5
  125. package/src/services/thread/thread-message.service.ts +24 -8
  126. package/src/services/thread/thread-title.service.ts +1 -1
  127. package/src/services/thread/thread-turn-execution.ts +1 -1
  128. package/src/services/thread/thread-turn-preparation.service.ts +18 -16
  129. package/src/services/thread/thread-turn-streaming.ts +12 -11
  130. package/src/services/thread/thread-turn.ts +43 -10
  131. package/src/services/thread/thread.service.ts +11 -2
  132. package/src/services/user.service.ts +1 -1
  133. package/src/services/write-intent-validator.service.ts +1 -1
  134. package/src/storage/attachment-storage.service.ts +7 -4
  135. package/src/storage/generated-document-storage.service.ts +1 -1
  136. package/src/system-agents/context-compaction.agent.ts +1 -1
  137. package/src/system-agents/helper-agent-options.ts +1 -1
  138. package/src/system-agents/memory-reranker.agent.ts +1 -1
  139. package/src/system-agents/memory.agent.ts +1 -1
  140. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  141. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  142. package/src/system-agents/skill-extractor.agent.ts +1 -1
  143. package/src/system-agents/skill-manager.agent.ts +1 -1
  144. package/src/system-agents/title-generator.agent.ts +1 -1
  145. package/src/tools/execution-plan.tool.ts +28 -17
  146. package/src/tools/fetch-webpage.tool.ts +20 -13
  147. package/src/tools/firecrawl-client.ts +13 -3
  148. package/src/tools/plan-approval.tool.ts +9 -1
  149. package/src/tools/search-web.tool.ts +16 -9
  150. package/src/tools/team-think.tool.ts +2 -2
  151. package/src/utils/async.ts +15 -6
  152. package/src/utils/errors.ts +27 -15
  153. package/src/workers/bootstrap.ts +25 -48
  154. package/src/workers/organization-learning.worker.ts +1 -1
  155. package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
  156. package/src/workers/worker-utils.ts +20 -2
  157. package/src/config/search.ts +0 -3
  158. package/src/runtime/agent-types.ts +0 -1
@@ -1,3 +1,5 @@
1
+ import { Match } from 'effect'
2
+
1
3
  import { clampImportance, compactWhitespace } from '../../utils/string'
2
4
 
3
5
  const SCORE_WEIGHTS = {
@@ -123,20 +125,17 @@ function scoreFact<T extends MemoryFactInput>(fact: T): number {
123
125
  const durability = fact.durability ?? 'standard'
124
126
  const type = fact.type ?? 'fact'
125
127
 
126
- const durabilityWeight =
127
- durability === 'core'
128
- ? SCORE_WEIGHTS.durability.core
129
- : durability === 'standard'
130
- ? SCORE_WEIGHTS.durability.standard
131
- : SCORE_WEIGHTS.durability.weak
132
- const typeWeight =
133
- type === 'decision'
134
- ? SCORE_WEIGHTS.type.decision
135
- : type === 'fact'
136
- ? SCORE_WEIGHTS.type.fact
137
- : type === 'preference'
138
- ? SCORE_WEIGHTS.type.preference
139
- : SCORE_WEIGHTS.type.default
128
+ const durabilityWeight = Match.value(durability).pipe(
129
+ Match.when('core', () => SCORE_WEIGHTS.durability.core),
130
+ Match.when('standard', () => SCORE_WEIGHTS.durability.standard),
131
+ Match.orElse(() => SCORE_WEIGHTS.durability.weak),
132
+ )
133
+ const typeWeight = Match.value(type).pipe(
134
+ Match.when('decision', () => SCORE_WEIGHTS.type.decision),
135
+ Match.when('fact', () => SCORE_WEIGHTS.type.fact),
136
+ Match.when('preference', () => SCORE_WEIGHTS.type.preference),
137
+ Match.orElse(() => SCORE_WEIGHTS.type.default),
138
+ )
140
139
  const lengthWeight =
141
140
  Math.min(fact.content.length, SCORE_WEIGHTS.maxContentLength) / SCORE_WEIGHTS.maxContentLength / 10
142
141
 
@@ -340,61 +339,49 @@ export function compileMemoryUpdatesFromDelta(params: {
340
339
  deleteSet.add(invalidateId)
341
340
  }
342
341
 
343
- if (item.classification === 'duplicate') {
344
- const targetId = item.targetMemoryIds[0]
345
- if (targetId) {
346
- updates.push({ id: targetId, text: existingById.get(targetId) ?? item.fact, event: 'NONE' })
347
- } else {
348
- updates.push({ id: `noop_${item.index}`, text: item.fact, event: 'NONE' })
349
- }
350
- continue
351
- }
352
-
353
- if (item.classification === 'enriches') {
354
- const targetId = item.targetMemoryIds[0]
355
- if (targetId) {
356
- const relations = buildRelations(item, 'new').filter((relation) => relation.memoryId !== targetId)
357
- updates.push({
358
- id: targetId,
359
- text: item.fact,
360
- event: 'UPDATE',
361
- oldMemory: existingById.get(targetId),
362
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
363
- })
364
- continue
365
- }
342
+ const nextUpdates = Match.value(item.classification).pipe(
343
+ Match.when('duplicate', (): MemoryUpdateItemLike[] => {
344
+ const targetId = item.targetMemoryIds[0]
345
+ if (targetId) {
346
+ return [{ id: targetId, text: existingById.get(targetId) ?? item.fact, event: 'NONE' }]
347
+ }
348
+ return [{ id: `noop_${item.index}`, text: item.fact, event: 'NONE' }]
349
+ }),
350
+ Match.when('enriches', (): MemoryUpdateItemLike[] => {
351
+ const targetId = item.targetMemoryIds[0]
352
+ if (targetId) {
353
+ const relations = buildRelations(item, 'new').filter((relation) => relation.memoryId !== targetId)
354
+ return [
355
+ {
356
+ id: targetId,
357
+ text: item.fact,
358
+ event: 'UPDATE',
359
+ oldMemory: existingById.get(targetId),
360
+ ...(relations.length > 0 ? { relatesTo: relations } : {}),
361
+ },
362
+ ]
363
+ }
366
364
 
367
- const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
368
- const relations = buildRelations(item, 'new')
369
- updates.push({
370
- id: addId,
371
- text: item.fact,
372
- event: 'ADD',
373
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
374
- })
375
- continue
376
- }
365
+ const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
366
+ const relations = buildRelations(item, 'new')
367
+ return [{ id: addId, text: item.fact, event: 'ADD', ...(relations.length > 0 ? { relatesTo: relations } : {}) }]
368
+ }),
369
+ Match.whenOr('supersedes', 'contradicts', (classification): MemoryUpdateItemLike[] => {
370
+ const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
371
+ const relations = buildRelations(item, classification)
372
+ return [{ id: addId, text: item.fact, event: 'ADD', ...(relations.length > 0 ? { relatesTo: relations } : {}) }]
373
+ }),
374
+ Match.when('new', (): MemoryUpdateItemLike[] => {
375
+ const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
376
+ const relations = buildRelations(item, 'new')
377
+ return [{ id: addId, text: item.fact, event: 'ADD', ...(relations.length > 0 ? { relatesTo: relations } : {}) }]
378
+ }),
379
+ Match.exhaustive,
380
+ )
377
381
 
378
- if (item.classification === 'supersedes' || item.classification === 'contradicts') {
379
- const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
380
- const relations = buildRelations(item, item.classification)
381
- updates.push({
382
- id: addId,
383
- text: item.fact,
384
- event: 'ADD',
385
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
386
- })
387
- continue
382
+ for (const update of nextUpdates) {
383
+ updates.push(update)
388
384
  }
389
-
390
- const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
391
- const relations = buildRelations(item, 'new')
392
- updates.push({
393
- id: addId,
394
- text: item.fact,
395
- event: 'ADD',
396
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
397
- })
398
385
  }
399
386
 
400
387
  const preservedRelationTargetIds = new Set<string>()
@@ -1,4 +1,4 @@
1
- import { Schema } from 'effect'
1
+ import { Effect, Schema } from 'effect'
2
2
 
3
3
  export const ORG_SCOPE_PREFIX = 'org'
4
4
 
@@ -9,41 +9,45 @@ export class MemoryScopeError extends Schema.TaggedErrorClass<MemoryScopeError>(
9
9
  message: Schema.String,
10
10
  }) {}
11
11
 
12
- function stripRecordPrefix(id: string): string {
12
+ function stripRecordPrefix(id: string): Effect.Effect<string, MemoryScopeError> {
13
13
  if (typeof id !== 'string' || id.length === 0) {
14
- throw new MemoryScopeError({ message: 'id must be a non-empty string' })
14
+ return Effect.fail(new MemoryScopeError({ message: 'id must be a non-empty string' }))
15
15
  }
16
16
  const [, ...rest] = id.split(':')
17
- return rest.length > 0 ? rest.join(':') : id
17
+ return Effect.succeed(rest.length > 0 ? rest.join(':') : id)
18
18
  }
19
19
 
20
- export function scopeId(prefix: string, id: string): string {
21
- if (typeof prefix !== 'string' || prefix.length === 0) {
22
- throw new MemoryScopeError({ message: 'prefix must be a non-empty string' })
23
- }
24
- if (!SCOPE_PREFIX_REGEX.test(prefix)) {
25
- throw new MemoryScopeError({ message: `Invalid scope prefix: ${prefix}` })
26
- }
27
-
28
- const stripped = stripRecordPrefix(id)
29
- const scoped = `${prefix}:${stripped}`
30
- if (scoped.length > SCOPE_ID_MAX_LENGTH) {
31
- throw new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
32
- }
33
-
34
- return scoped
20
+ export function scopeId(prefix: string, id: string): Effect.Effect<string, MemoryScopeError> {
21
+ return Effect.gen(function* () {
22
+ if (typeof prefix !== 'string' || prefix.length === 0) {
23
+ return yield* new MemoryScopeError({ message: 'prefix must be a non-empty string' })
24
+ }
25
+ if (!SCOPE_PREFIX_REGEX.test(prefix)) {
26
+ return yield* new MemoryScopeError({ message: `Invalid scope prefix: ${prefix}` })
27
+ }
28
+
29
+ const stripped = yield* stripRecordPrefix(id)
30
+ const scoped = `${prefix}:${stripped}`
31
+ if (scoped.length > SCOPE_ID_MAX_LENGTH) {
32
+ return yield* new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
33
+ }
34
+
35
+ return scoped
36
+ })
35
37
  }
36
38
 
37
- export function agentScopeId(orgId: string, agentName: string): string {
38
- if (typeof agentName !== 'string' || agentName.trim().length === 0) {
39
- throw new MemoryScopeError({ message: 'agentName must be a non-empty string' })
40
- }
41
- const strippedOrgId = stripRecordPrefix(orgId)
42
- const scoped = `agent:${strippedOrgId}:${agentName.trim()}`
39
+ export function agentScopeId(orgId: string, agentName: string): Effect.Effect<string, MemoryScopeError> {
40
+ return Effect.gen(function* () {
41
+ if (typeof agentName !== 'string' || agentName.trim().length === 0) {
42
+ return yield* new MemoryScopeError({ message: 'agentName must be a non-empty string' })
43
+ }
44
+ const strippedOrgId = yield* stripRecordPrefix(orgId)
45
+ const scoped = `agent:${strippedOrgId}:${agentName.trim()}`
43
46
 
44
- if (scoped.length > SCOPE_ID_MAX_LENGTH) {
45
- throw new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
46
- }
47
+ if (scoped.length > SCOPE_ID_MAX_LENGTH) {
48
+ return yield* new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
49
+ }
47
50
 
48
- return scoped
51
+ return scoped
52
+ })
49
53
  }
@@ -35,63 +35,42 @@ function readFunctionEffect(
35
35
  return Effect.succeed(value as (...args: unknown[]) => PromiseLike<unknown>)
36
36
  }
37
37
 
38
+ function configError(message: string): PluginResolutionError {
39
+ return new PluginResolutionError({ stage: 'configuration', message, cause: undefined })
40
+ }
41
+
42
+ function requireRecord(value: unknown, message: string): Effect.Effect<Record<string, unknown>, PluginResolutionError> {
43
+ return isRecord(value) ? Effect.succeed(value) : Effect.fail(configError(message))
44
+ }
45
+
38
46
  function resolvePluginServiceEffect(
39
47
  pluginName: string,
40
48
  serviceName: string,
41
49
  methodName: string,
42
50
  ): Effect.Effect<(...args: unknown[]) => PromiseLike<unknown>, PluginResolutionError> {
43
- const pluginRuntime = getPluginRuntime()
44
- if (!pluginRuntime) {
45
- return Effect.fail(
46
- new PluginResolutionError({
47
- stage: 'configuration',
48
- message: `Plugin runtime is not configured. Missing "${pluginName}" integration.`,
49
- cause: undefined,
50
- }),
51
- )
52
- }
53
-
54
- const plugin = pluginRuntime[pluginName]
55
- if (!isRecord(plugin)) {
56
- return Effect.fail(
57
- new PluginResolutionError({
58
- stage: 'configuration',
59
- message: `Plugin "${pluginName}" is not configured in the current runtime.`,
60
- cause: undefined,
61
- }),
62
- )
63
- }
64
-
65
- const services = plugin.services
66
- if (!isRecord(services)) {
67
- return Effect.fail(
68
- new PluginResolutionError({
69
- stage: 'configuration',
70
- message: `Plugin "${pluginName}" does not expose a services registry.`,
71
- cause: undefined,
72
- }),
73
- )
74
- }
75
-
76
- const service = services[serviceName]
77
- if (!isRecord(service)) {
78
- return Effect.fail(
79
- new PluginResolutionError({
80
- stage: 'configuration',
81
- message: `Plugin "${pluginName}" service "${serviceName}" is not configured.`,
82
- cause: undefined,
83
- }),
84
- )
85
- }
86
-
87
- return readFunctionEffect(
88
- service[methodName],
89
- `Plugin "${pluginName}" service "${serviceName}" is missing method "${methodName}".`,
90
- ).pipe(
91
- Effect.map(
92
- (method) =>
93
- (...args: unknown[]) =>
94
- Reflect.apply(method, service, args),
51
+ return Effect.fromNullishOr(getPluginRuntime()).pipe(
52
+ Effect.mapError(() => configError(`Plugin runtime is not configured. Missing "${pluginName}" integration.`)),
53
+ Effect.flatMap((pluginRuntime) =>
54
+ requireRecord(pluginRuntime[pluginName], `Plugin "${pluginName}" is not configured in the current runtime.`),
55
+ ),
56
+ Effect.flatMap((plugin) =>
57
+ requireRecord(plugin.services, `Plugin "${pluginName}" does not expose a services registry.`),
58
+ ),
59
+ Effect.flatMap((services) =>
60
+ requireRecord(services[serviceName], `Plugin "${pluginName}" service "${serviceName}" is not configured.`).pipe(
61
+ Effect.flatMap((service) =>
62
+ readFunctionEffect(
63
+ service[methodName],
64
+ `Plugin "${pluginName}" service "${serviceName}" is missing method "${methodName}".`,
65
+ ).pipe(
66
+ Effect.map(
67
+ (method) =>
68
+ (...args: unknown[]) =>
69
+ Reflect.apply(method, service, args),
70
+ ),
71
+ ),
72
+ ),
73
+ ),
95
74
  ),
96
75
  )
97
76
  }
@@ -100,8 +79,8 @@ function tryResolvePluginServiceEffect(
100
79
  pluginName: string,
101
80
  serviceName: string,
102
81
  methodName: string,
103
- ): Effect.Effect<((...args: unknown[]) => PromiseLike<unknown>) | undefined> {
104
- return Effect.catch(resolvePluginServiceEffect(pluginName, serviceName, methodName), () => Effect.succeed(undefined))
82
+ ): Effect.Effect<((...args: unknown[]) => PromiseLike<unknown>) | void, never, never> {
83
+ return Effect.catch(resolvePluginServiceEffect(pluginName, serviceName, methodName), () => Effect.void)
105
84
  }
106
85
 
107
86
  export function getLinearInstallationByOrgId(organizationId: RecordIdRef): Promise<unknown> {
@@ -1,10 +1,9 @@
1
1
  import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import { Schema, Effect } from 'effect'
3
- import type { Context } from 'effect'
4
3
 
5
4
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
6
5
  import type { RecordIdRef } from '../db/record-id'
7
- import { getCurrentRuntime } from '../effect/runtime-ref'
6
+ import { runPromise } from '../effect/runtime'
8
7
  import { AgentConfigServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
9
8
  import { enqueueMemoryConsolidation } from '../queues/memory-consolidation.queue'
10
9
  import { enqueueRegularChatMemoryDigest, enqueueSkillExtraction } from '../queues/organization-learning.queue'
@@ -21,7 +20,6 @@ import {
21
20
  shouldEnqueueOnboardingPostChatMemory,
22
21
  shouldEnqueueRegularDigestForThread,
23
22
  } from './memory/memory-digest-policy'
24
- import type { LotaRuntimeAdapters } from './runtime-extensions'
25
23
  import { shouldEnqueueSkillExtraction } from './skill-extraction-policy'
26
24
  import {
27
25
  appendPersistedThreadContextToHistoryMessages,
@@ -33,13 +31,6 @@ import {
33
31
  toHistoryMessages,
34
32
  } from './thread-chat-helpers'
35
33
 
36
- interface PostTurnSideEffectsServices {
37
- recentActivityService: Context.Service.Shape<typeof RecentActivityServiceTag>
38
- threadService: Context.Service.Shape<typeof ThreadServiceTag>
39
- agentConfig: ResolvedAgentConfig
40
- runtimeAdapters: LotaRuntimeAdapters
41
- }
42
-
43
34
  class PostTurnSideEffectsError extends Schema.TaggedErrorClass<PostTurnSideEffectsError>()('PostTurnSideEffectsError', {
44
35
  message: Schema.String,
45
36
  cause: Schema.Defect,
@@ -55,20 +46,6 @@ function tryPostTurnSideEffect<A>(
55
46
  })
56
47
  }
57
48
 
58
- function getPostTurnSideEffectsServices(): PostTurnSideEffectsServices {
59
- const runtime = getCurrentRuntime()
60
- return runtime.runSync(
61
- Effect.gen(function* () {
62
- return {
63
- recentActivityService: yield* RecentActivityServiceTag,
64
- threadService: yield* ThreadServiceTag,
65
- agentConfig: yield* AgentConfigServiceTag,
66
- runtimeAdapters: yield* RuntimeAdaptersServiceTag,
67
- }
68
- }),
69
- )
70
- }
71
-
72
49
  function resolveDisplayName(
73
50
  agentConfig: ResolvedAgentConfig,
74
51
  agentId: string,
@@ -139,7 +116,10 @@ interface PostTurnSideEffectsParams {
139
116
 
140
117
  const runPostTurnSideEffectsEffect = (params: PostTurnSideEffectsParams) =>
141
118
  Effect.gen(function* () {
142
- const { recentActivityService, threadService, agentConfig, runtimeAdapters } = getPostTurnSideEffectsServices()
119
+ const recentActivityService = yield* RecentActivityServiceTag
120
+ const threadService = yield* ThreadServiceTag
121
+ const agentConfig: ResolvedAgentConfig = yield* AgentConfigServiceTag
122
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
143
123
  const recentHistory = yield* tryPostTurnSideEffect('Failed to load recent thread history.', () =>
144
124
  params.loadRecentHistory(),
145
125
  )
@@ -286,5 +266,5 @@ const runPostTurnSideEffectsEffect = (params: PostTurnSideEffectsParams) =>
286
266
  })
287
267
 
288
268
  export function runPostTurnSideEffects(params: PostTurnSideEffectsParams): Promise<void> {
289
- return Effect.runPromise(runPostTurnSideEffectsEffect(params))
269
+ return runPromise(runPostTurnSideEffectsEffect(params))
290
270
  }
@@ -1,8 +1,8 @@
1
1
  import { Schema, Effect } from 'effect'
2
2
 
3
- interface ScopedRetrievalTask<TCandidate> {
3
+ interface ScopedRetrievalTask<TCandidate, E = never> {
4
4
  scopeTag: string
5
- retrieve: () => PromiseLike<TCandidate[]> | Effect.Effect<TCandidate[], unknown>
5
+ retrieve: () => PromiseLike<TCandidate[]> | Effect.Effect<TCandidate[], E>
6
6
  }
7
7
 
8
8
  interface ScopedRetrievalResult<TCandidate> {
@@ -20,8 +20,8 @@ function toScopedRetrievalError(scopeTag: string, cause: unknown): ScopedRetriev
20
20
  return new ScopedRetrievalError({ scopeTag, message: cause instanceof Error ? cause.message : String(cause), cause })
21
21
  }
22
22
 
23
- export function executeScopedRetrieval<TCandidate>(
24
- tasks: ScopedRetrievalTask<TCandidate>[],
23
+ export function executeScopedRetrieval<TCandidate, E>(
24
+ tasks: ScopedRetrievalTask<TCandidate, E>[],
25
25
  ): Effect.Effect<ScopedRetrievalResult<TCandidate>[], ScopedRetrievalError> {
26
26
  return Effect.forEach(
27
27
  tasks,
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Installs and tears down the SDK's module-level runtime accessors.
3
+ *
4
+ * Several SDK modules expose `configure*`/`clear*` pairs so host code can read
5
+ * runtime-resolved services synchronously (AI gateway, agent defaults,
6
+ * firecrawl, graph designer, queue jobs, runtime extensions, redis, thread
7
+ * defaults). `createLotaRuntime` wires them once at boot and clears them on
8
+ * disconnect; this module keeps both phases in a single place so they stay
9
+ * symmetric.
10
+ */
11
+
12
+ import type { Context, ManagedRuntime } from 'effect'
13
+ import { Effect } from 'effect'
14
+
15
+ import {
16
+ AiGatewayTag,
17
+ clearAiGatewayRuntimeAccessors,
18
+ configureAiGatewayRuntimeAccessors,
19
+ } from '../ai-gateway/ai-gateway'
20
+ import { clearAgentRuntimeDefaults, configureAgentRuntimeDefaults } from '../config/agent-defaults'
21
+ import { clearThreadRuntimeDefaults, configureThreadRuntimeDefaults } from '../config/thread-defaults'
22
+ import {
23
+ AgentConfigServiceTag,
24
+ AgentFactoryServiceTag,
25
+ RuntimeAdaptersServiceTag,
26
+ RuntimeWorkerExtensionsServiceTag,
27
+ ThreadConfigServiceTag,
28
+ ToolProvidersServiceTag,
29
+ TurnHooksServiceTag,
30
+ } from '../effect'
31
+ import type { RedisConnectionManager } from '../redis/connection'
32
+ import { clearRuntimeRedisManager, configureRuntimeRedisManager } from '../redis/runtime-connection'
33
+ import { QueueJobServiceTag } from '../services/queue-job.service'
34
+ import { FirecrawlTag, clearFirecrawlClient, configureFirecrawlClient } from '../tools/firecrawl-client'
35
+ import { clearQueueJobService, configureQueueJobService } from '../workers/worker-utils'
36
+ import { clearGraphDesigner, configureGraphDesigner } from './graph-designer'
37
+ import type { ResolvedLotaRuntimeConfig } from './runtime-config'
38
+ import { clearRuntimeExtensionsAccessors, configureRuntimeExtensionsAccessors } from './runtime-extensions'
39
+
40
+ // eslint-disable-next-line typescript-eslint/no-explicit-any -- ManagedRuntime is contravariant in R; `any` is the only valid wildcard
41
+ type SdkManagedRuntime = ManagedRuntime.ManagedRuntime<any, any>
42
+
43
+ interface ConfigureRuntimeAccessorsInput {
44
+ managedRuntime: SdkManagedRuntime
45
+ runtimeConfig: ResolvedLotaRuntimeConfig
46
+ redisManager: RedisConnectionManager
47
+ }
48
+
49
+ /**
50
+ * Resolve every service the SDK exposes through module-level accessors and
51
+ * install them. Must be called after `setLotaSdkRuntime(managedRuntime)`.
52
+ */
53
+ export function configureRuntimeModuleAccessors(input: ConfigureRuntimeAccessorsInput): void {
54
+ const { managedRuntime, runtimeConfig, redisManager } = input
55
+ const resolve = <I, T>(tag: Context.Key<I, T>): T =>
56
+ // We know every tag is provided by the managed runtime; the cast bridges
57
+ // the erased service-set carried by `SdkManagedRuntime`.
58
+ managedRuntime.runSync(Effect.service(tag) as Effect.Effect<T, never, never>)
59
+
60
+ configureAiGatewayRuntimeAccessors({ aiGateway: resolve(AiGatewayTag), runtimeConfig })
61
+ configureAgentRuntimeDefaults({
62
+ agentConfig: resolve(AgentConfigServiceTag),
63
+ agentFactoryConfig: resolve(AgentFactoryServiceTag),
64
+ })
65
+ configureThreadRuntimeDefaults(resolve(ThreadConfigServiceTag))
66
+ configureRuntimeExtensionsAccessors({
67
+ adapters: resolve(RuntimeAdaptersServiceTag),
68
+ turnHooks: resolve(TurnHooksServiceTag),
69
+ toolProviders: resolve(ToolProvidersServiceTag),
70
+ extraWorkers: resolve(RuntimeWorkerExtensionsServiceTag),
71
+ })
72
+ configureFirecrawlClient(resolve(FirecrawlTag))
73
+ configureQueueJobService(resolve(QueueJobServiceTag))
74
+ configureRuntimeRedisManager(redisManager)
75
+ configureGraphDesigner(runtimeConfig.graphDesigner)
76
+ }
77
+
78
+ /**
79
+ * Clears every module-level accessor installed by
80
+ * `configureRuntimeModuleAccessors`. Always safe to call (idempotent per clear
81
+ * function).
82
+ */
83
+ export function clearRuntimeModuleAccessors(): void {
84
+ clearAiGatewayRuntimeAccessors()
85
+ clearAgentRuntimeDefaults()
86
+ clearFirecrawlClient()
87
+ clearGraphDesigner()
88
+ clearQueueJobService()
89
+ clearRuntimeExtensionsAccessors()
90
+ clearRuntimeRedisManager()
91
+ clearThreadRuntimeDefaults()
92
+ }
@@ -329,7 +329,7 @@ export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>)
329
329
  return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
330
330
  }
331
331
 
332
- const runtimeEnvironmentConfig = Config.all({
332
+ export const lotaRuntimeEnvConfig = Config.all({
333
333
  surrealdbUrl: Config.string('SURREALDB_URL'),
334
334
  surrealdbNamespace: Config.string('SURREALDB_NAMESPACE'),
335
335
  surrealdbUser: Config.string('SURREALDB_USER'),
@@ -369,7 +369,7 @@ export function loadLotaRuntimeConfigFromEnv(
369
369
  overrides: LotaRuntimeEnvironmentOverrides,
370
370
  options: { configProvider?: ConfigProvider.ConfigProvider } = {},
371
371
  ) {
372
- return runtimeEnvironmentConfig
372
+ return lotaRuntimeEnvConfig
373
373
  .parse(options.configProvider ?? ConfigProvider.fromEnv())
374
374
  .pipe(
375
375
  Effect.map((env) =>
@@ -403,7 +403,7 @@ export function loadLotaRuntimeConfigFromEnv(
403
403
  apiKey: Redacted.value(env.firecrawlApiKey),
404
404
  ...(Option.isSome(env.firecrawlApiBaseUrl) ? { apiBaseUrl: env.firecrawlApiBaseUrl.value } : {}),
405
405
  },
406
- logging: overrides.logging ?? { level: env.logLevel as (typeof logLevelValues)[number] },
406
+ logging: overrides.logging ?? { level: z.enum(logLevelValues).parse(env.logLevel) },
407
407
  memory: {
408
408
  searchK: env.memorySearchK,
409
409
  rerankerStrategy: env.memoryRerankerStrategy as MemoryRerankerStrategy,
@@ -6,11 +6,9 @@ import type {
6
6
  PlanSpecRecord,
7
7
  } from '@lota-sdk/shared'
8
8
  import type { ToolSet } from 'ai'
9
- import { Effect } from 'effect'
10
- import type { Context } from 'effect'
11
9
 
12
10
  import type { RecordIdRef } from '../db/record-id'
13
- import { getCurrentRuntime } from '../effect/runtime-ref'
11
+ import { resolveLotaService } from '../effect/runtime'
14
12
  import {
15
13
  RuntimeAdaptersServiceTag,
16
14
  RuntimeWorkerExtensionsServiceTag,
@@ -202,20 +200,33 @@ export interface LotaRuntimeAdapters {
202
200
  withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
203
201
  }
204
202
 
205
- function resolveFromRuntime<I, T>(tag: Context.Key<I, T>): T {
206
- return getCurrentRuntime().runSync(Effect.service(tag))
203
+ interface RuntimeExtensionsAccessors {
204
+ adapters: LotaRuntimeAdapters
205
+ turnHooks: LotaRuntimeTurnHooks
206
+ toolProviders: ToolSet
207
+ extraWorkers: LotaRuntimeWorkerExtensions
208
+ }
209
+
210
+ let runtimeExtensionsAccessors: RuntimeExtensionsAccessors | null = null
211
+
212
+ export function configureRuntimeExtensionsAccessors(accessors: RuntimeExtensionsAccessors): void {
213
+ runtimeExtensionsAccessors = accessors
214
+ }
215
+
216
+ export function clearRuntimeExtensionsAccessors(): void {
217
+ runtimeExtensionsAccessors = null
207
218
  }
208
219
 
209
220
  export function getRuntimeAdapters(): LotaRuntimeAdapters {
210
- return resolveFromRuntime(RuntimeAdaptersServiceTag)
221
+ return runtimeExtensionsAccessors?.adapters ?? resolveLotaService(RuntimeAdaptersServiceTag)
211
222
  }
212
223
 
213
224
  export function getTurnHooks(): LotaRuntimeTurnHooks {
214
- return resolveFromRuntime(TurnHooksServiceTag)
225
+ return runtimeExtensionsAccessors?.turnHooks ?? resolveLotaService(TurnHooksServiceTag)
215
226
  }
216
227
 
217
228
  export function getToolProviders(): ToolSet {
218
- return resolveFromRuntime(ToolProvidersServiceTag)
229
+ return runtimeExtensionsAccessors?.toolProviders ?? resolveLotaService(ToolProvidersServiceTag)
219
230
  }
220
231
 
221
232
  export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) | undefined {
@@ -223,7 +234,7 @@ export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) |
223
234
  }
224
235
 
225
236
  export function getExtraWorkers(): LotaRuntimeWorkerExtensions {
226
- return resolveFromRuntime(RuntimeWorkerExtensionsServiceTag)
237
+ return runtimeExtensionsAccessors?.extraWorkers ?? resolveLotaService(RuntimeWorkerExtensionsServiceTag)
227
238
  }
228
239
 
229
240
  export function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {