@lota-sdk/core 0.4.12 → 0.4.14

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 (139) hide show
  1. package/package.json +4 -4
  2. package/src/ai/embedding-cache.ts +17 -11
  3. package/src/ai-gateway/ai-gateway.ts +164 -94
  4. package/src/ai-gateway/index.ts +4 -1
  5. package/src/config/agent-defaults.ts +2 -2
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/create-runtime.ts +259 -200
  8. package/src/db/cursor-pagination.ts +2 -9
  9. package/src/db/memory-store.ts +194 -175
  10. package/src/db/memory.ts +125 -71
  11. package/src/db/schema-fingerprint.ts +5 -4
  12. package/src/db/service-normalization.ts +4 -3
  13. package/src/db/service.ts +3 -2
  14. package/src/db/startup.ts +15 -16
  15. package/src/effect/errors.ts +161 -21
  16. package/src/effect/index.ts +0 -1
  17. package/src/embeddings/provider.ts +15 -7
  18. package/src/queues/autonomous-job.queue.ts +10 -22
  19. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  20. package/src/queues/document-processor.queue.ts +13 -4
  21. package/src/queues/memory-consolidation.queue.ts +26 -14
  22. package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
  23. package/src/queues/plan-scheduler.queue.ts +37 -15
  24. package/src/queues/queue-factory.ts +59 -35
  25. package/src/queues/standalone-worker.ts +3 -2
  26. package/src/redis/connection.ts +10 -3
  27. package/src/redis/org-memory-lock.ts +1 -1
  28. package/src/redis/redis-lease-lock.ts +5 -5
  29. package/src/redis/stream-context.ts +1 -1
  30. package/src/runtime/chat-message.ts +64 -1
  31. package/src/runtime/chat-run-orchestration.ts +33 -20
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  33. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  34. package/src/runtime/domain-layer.ts +13 -7
  35. package/src/runtime/execution-plan.ts +7 -3
  36. package/src/runtime/live-turn-trace.ts +6 -49
  37. package/src/runtime/memory/memory-block.ts +3 -9
  38. package/src/runtime/memory/memory-scope.ts +3 -1
  39. package/src/runtime/plugin-resolution.ts +2 -1
  40. package/src/runtime/post-turn-side-effects.ts +6 -5
  41. package/src/runtime/retrieval-adapters.ts +8 -20
  42. package/src/runtime/runtime-config.ts +3 -9
  43. package/src/runtime/runtime-extensions.ts +2 -4
  44. package/src/runtime/runtime-lifecycle.ts +56 -16
  45. package/src/runtime/runtime-services.ts +180 -102
  46. package/src/runtime/runtime-worker-registry.ts +3 -1
  47. package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
  48. package/src/runtime/social-chat/social-chat-history.ts +21 -18
  49. package/src/runtime/social-chat/social-chat.ts +356 -223
  50. package/src/runtime/specialist-runner.ts +3 -1
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
  52. package/src/runtime/thread-turn-context.ts +142 -102
  53. package/src/runtime/turn-lifecycle.ts +15 -46
  54. package/src/services/agent-activity.service.ts +1 -1
  55. package/src/services/agent-executor.service.ts +107 -77
  56. package/src/services/autonomous-job.service.ts +354 -293
  57. package/src/services/background-work.service.ts +3 -3
  58. package/src/services/context-compaction.service.ts +7 -2
  59. package/src/services/document-chunk.service.ts +50 -32
  60. package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
  61. package/src/services/execution-plan/execution-plan.service.ts +162 -179
  62. package/src/services/feedback-loop.service.ts +5 -4
  63. package/src/services/graph-full-routing.ts +37 -36
  64. package/src/services/institutional-memory.service.ts +28 -30
  65. package/src/services/learned-skill.service.ts +107 -72
  66. package/src/services/memory/memory-errors.ts +4 -23
  67. package/src/services/memory/memory-org-memory.ts +10 -5
  68. package/src/services/memory/memory-rerank.ts +18 -6
  69. package/src/services/memory/memory.service.ts +170 -111
  70. package/src/services/memory/rerank.service.ts +29 -20
  71. package/src/services/organization-member.service.ts +1 -1
  72. package/src/services/organization.service.ts +69 -75
  73. package/src/services/ownership-dispatcher.service.ts +40 -39
  74. package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
  75. package/src/services/plan/plan-agent-query.service.ts +39 -31
  76. package/src/services/plan/plan-completion-side-effects.ts +13 -17
  77. package/src/services/plan/plan-coordination.service.ts +2 -1
  78. package/src/services/plan/plan-cycle.service.ts +6 -5
  79. package/src/services/plan/plan-deadline.service.ts +57 -54
  80. package/src/services/plan/plan-event-delivery.service.ts +5 -4
  81. package/src/services/plan/plan-executor-graph.ts +18 -15
  82. package/src/services/plan/plan-executor.service.ts +235 -262
  83. package/src/services/plan/plan-run.service.ts +169 -93
  84. package/src/services/plan/plan-scheduler.service.ts +192 -202
  85. package/src/services/plan/plan-template.service.ts +1 -1
  86. package/src/services/plan/plan-transaction-events.ts +1 -1
  87. package/src/services/plan/plan-workspace.service.ts +23 -14
  88. package/src/services/plugin-executor.service.ts +5 -9
  89. package/src/services/queue-job.service.ts +117 -59
  90. package/src/services/recent-activity-title.service.ts +13 -12
  91. package/src/services/recent-activity.service.ts +6 -1
  92. package/src/services/social-chat-history.service.ts +29 -25
  93. package/src/services/system-executor.service.ts +5 -9
  94. package/src/services/thread/thread-active-run.ts +2 -2
  95. package/src/services/thread/thread-listing.ts +61 -57
  96. package/src/services/thread/thread-memory-block.ts +73 -48
  97. package/src/services/thread/thread-message.service.ts +76 -65
  98. package/src/services/thread/thread-record-store.ts +8 -8
  99. package/src/services/thread/thread-title.service.ts +10 -4
  100. package/src/services/thread/thread-turn-execution.ts +43 -45
  101. package/src/services/thread/thread-turn-preparation.service.ts +257 -135
  102. package/src/services/thread/thread-turn-streaming.ts +82 -85
  103. package/src/services/thread/thread-turn.ts +8 -8
  104. package/src/services/thread/thread.service.ts +135 -100
  105. package/src/services/user.service.ts +45 -48
  106. package/src/storage/attachment-parser.ts +6 -2
  107. package/src/storage/attachment-storage.service.ts +5 -6
  108. package/src/storage/generated-document-storage.service.ts +1 -1
  109. package/src/system-agents/context-compaction.agent.ts +10 -9
  110. package/src/system-agents/delegated-agent-factory.ts +30 -6
  111. package/src/system-agents/memory-reranker.agent.ts +10 -9
  112. package/src/system-agents/memory.agent.ts +10 -9
  113. package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
  114. package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
  115. package/src/system-agents/skill-extractor.agent.ts +13 -12
  116. package/src/system-agents/skill-manager.agent.ts +13 -12
  117. package/src/system-agents/thread-router.agent.ts +10 -5
  118. package/src/system-agents/title-generator.agent.ts +13 -12
  119. package/src/tools/fetch-webpage.tool.ts +13 -13
  120. package/src/tools/memory-block.tool.ts +3 -1
  121. package/src/tools/plan-approval.tool.ts +4 -2
  122. package/src/tools/read-file-parts.tool.ts +10 -4
  123. package/src/tools/remember-memory.tool.ts +3 -1
  124. package/src/tools/research-topic.tool.ts +9 -5
  125. package/src/tools/search-web.tool.ts +16 -16
  126. package/src/tools/search.tool.ts +20 -5
  127. package/src/tools/team-think.tool.ts +61 -38
  128. package/src/utils/async.ts +5 -5
  129. package/src/utils/errors.ts +19 -18
  130. package/src/utils/sse-keepalive.ts +28 -25
  131. package/src/workers/bootstrap.ts +75 -11
  132. package/src/workers/memory-consolidation.worker.ts +82 -91
  133. package/src/workers/organization-learning.worker.ts +14 -4
  134. package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
  135. package/src/workers/skill-extraction.runner.ts +97 -61
  136. package/src/workers/utils/repo-structure-extractor.ts +13 -8
  137. package/src/workers/utils/thread-message-query.ts +24 -24
  138. package/src/workers/worker-utils.ts +23 -4
  139. package/src/effect/helpers.ts +0 -123
@@ -1,12 +1,25 @@
1
- import { Duration, Effect, Fiber } from 'effect'
1
+ import type { Fiber } from 'effect'
2
+ import { Duration, Effect, Exit, Schema, Scope } from 'effect'
3
+
4
+ import { ERROR_TAGS } from '../effect/errors'
2
5
 
3
6
  const KEEPALIVE_COMMENT = new TextEncoder().encode(': keepalive\n\n')
4
7
  const DEFAULT_KEEPALIVE_INTERVAL_MS = 20_000
5
8
 
9
+ class SseKeepaliveError extends Schema.TaggedErrorClass<SseKeepaliveError>()(ERROR_TAGS.SseKeepaliveError, {
10
+ phase: Schema.Literals(['read']),
11
+ cause: Schema.Defect,
12
+ }) {}
13
+
6
14
  /**
7
15
  * Wraps an SSE Response body with periodic keepalive comments.
8
16
  * SSE comments (`: keepalive\n\n`) are ignored by standard SSE parsers,
9
17
  * so no client changes are needed.
18
+ *
19
+ * Lifecycle: a function-local `Scope` owns both the keepalive and body-pump
20
+ * fibers via `Effect.forkScoped`. Stream `cancel` closes the scope, which
21
+ * auto-interrupts both fibers and runs their `ensuring` finalizers. No
22
+ * manual `Fiber.interrupt` bookkeeping.
10
23
  */
11
24
  export function wrapResponseWithKeepalive(response: Response, intervalMs = DEFAULT_KEEPALIVE_INTERVAL_MS): Response {
12
25
  const body = response.body
@@ -14,24 +27,10 @@ export function wrapResponseWithKeepalive(response: Response, intervalMs = DEFAU
14
27
 
15
28
  let closed = false
16
29
  let reader: ReadableStreamDefaultReader<Uint8Array> | null = null
17
- let keepaliveFiber: ReturnType<typeof Effect.runFork> | null = null
18
- let bodyPumpFiber: ReturnType<typeof Effect.runFork> | null = null
19
-
20
- const interruptFiber = (fiber: ReturnType<typeof Effect.runFork> | null) => {
21
- if (!fiber) return
22
- void Effect.runFork(Fiber.interrupt(fiber))
23
- }
30
+ const scope = Scope.makeUnsafe()
24
31
 
25
- const stopKeepalive = () => {
26
- const fiber = keepaliveFiber
27
- keepaliveFiber = null
28
- interruptFiber(fiber)
29
- }
30
-
31
- const stopBodyPump = () => {
32
- const fiber = bodyPumpFiber
33
- bodyPumpFiber = null
34
- interruptFiber(fiber)
32
+ const closeScope = () => {
33
+ void Effect.runPromise(Scope.close(scope, Exit.void))
35
34
  }
36
35
 
37
36
  const releaseReader = (bodyReader: ReadableStreamDefaultReader<Uint8Array>) => {
@@ -90,7 +89,10 @@ export function wrapResponseWithKeepalive(response: Response, intervalMs = DEFAU
90
89
  for (;;) {
91
90
  if (closed) return
92
91
 
93
- const { done, value } = yield* Effect.tryPromise(() => bodyReader.read())
92
+ const { done, value } = yield* Effect.tryPromise({
93
+ try: () => bodyReader.read(),
94
+ catch: (cause) => new SseKeepaliveError({ phase: 'read', cause }),
95
+ })
94
96
  if (done) {
95
97
  yield* Effect.sync(() => closeStream(controller))
96
98
  return
@@ -105,25 +107,26 @@ export function wrapResponseWithKeepalive(response: Response, intervalMs = DEFAU
105
107
  Effect.ensuring(
106
108
  Effect.sync(() => {
107
109
  closed = true
108
- stopKeepalive()
109
- bodyPumpFiber = null
110
110
  reader = null
111
111
  releaseReader(bodyReader)
112
+ closeScope()
112
113
  }),
113
114
  ),
114
115
  )
115
116
 
117
+ const startFiber = <A, E>(effect: Effect.Effect<A, E>): Fiber.Fiber<Fiber.Fiber<A, E>, never> =>
118
+ Effect.runFork(Scope.provide(Effect.forkScoped(effect), scope))
119
+
116
120
  const transformed = new ReadableStream<Uint8Array>({
117
121
  start(controller) {
118
122
  const bodyReader = body.getReader()
119
123
  reader = bodyReader
120
- keepaliveFiber = Effect.runFork(keepaliveEffect(controller))
121
- bodyPumpFiber = Effect.runFork(pumpBodyEffect(bodyReader, controller))
124
+ startFiber(keepaliveEffect(controller))
125
+ startFiber(pumpBodyEffect(bodyReader, controller))
122
126
  },
123
127
  cancel(reason) {
124
128
  closed = true
125
- stopKeepalive()
126
- stopBodyPump()
129
+ closeScope()
127
130
 
128
131
  const bodyReader = reader
129
132
  reader = null
@@ -1,17 +1,25 @@
1
- import { ConfigProvider, Option, Schema, Effect, Layer, ManagedRuntime, Redacted } from 'effect'
2
-
3
- import { AiGatewayLive } from '../ai-gateway/ai-gateway'
1
+ import { ConfigProvider, Deferred, Option, Schema, Effect, Layer, ManagedRuntime, Redacted } from 'effect'
2
+
3
+ import {
4
+ AiGatewayModelsTag,
5
+ AiGatewayTag,
6
+ RuntimeBridgeTag,
7
+ createAiGatewayModels,
8
+ makeAiGatewayService,
9
+ } from '../ai-gateway/ai-gateway'
10
+ import type { AiGatewayModels, RuntimeBridge } from '../ai-gateway/ai-gateway'
4
11
  import { EmbeddingCacheLive } from '../ai/embedding-cache'
5
12
  import { serverLogger } from '../config/logger'
6
13
  import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
7
14
  import { buildWorkerInfrastructureLayer } from '../effect'
15
+ import { ERROR_TAGS } from '../effect/errors'
8
16
  import { DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
9
17
  import { lotaRuntimeEnvConfig, parseLotaRuntimeConfig, parseWorkerBootstrapEnv } from '../runtime/runtime-config'
10
18
  import { FirecrawlLive } from '../tools/firecrawl-client'
11
19
  import { getErrorMessage } from '../utils/errors'
12
20
 
13
21
  class SandboxedWorkerBootstrapError extends Schema.TaggedErrorClass<SandboxedWorkerBootstrapError>()(
14
- 'SandboxedWorkerBootstrapError',
22
+ ERROR_TAGS.SandboxedWorkerBootstrapError,
15
23
  {
16
24
  stage: Schema.Literals(['setup', 'initialize', 'connect-db', 'connect-plugin-db', 'bootstrap-wait']),
17
25
  message: Schema.String,
@@ -86,12 +94,35 @@ function ensureSandboxedWorkerRuntimeConfigured(): Promise<WorkerManagedRuntime>
86
94
  const runtimeConfig = yield* buildSandboxedWorkerRuntimeConfigEffect()
87
95
 
88
96
  const infrastructureLayer = buildWorkerInfrastructureLayer(runtimeConfig)
89
-
90
- const layer = Layer.mergeAll(infrastructureLayer, Layer.provide(AiGatewayLive, infrastructureLayer))
91
-
92
- const fullLayer = Layer.mergeAll(layer, Layer.provide(Layer.mergeAll(EmbeddingCacheLive, FirecrawlLive), layer))
97
+ const aiGateway = yield* makeAiGatewayService(runtimeConfig).pipe(
98
+ Effect.mapError((error) => toSandboxedWorkerBootstrapError('setup', error)),
99
+ )
100
+ const aiGatewayModelsDeferred = yield* Deferred.make<AiGatewayModels>()
101
+ const runtimeBridgeDeferred = yield* Deferred.make<RuntimeBridge>()
102
+ const bridgeLayer = Layer.mergeAll(
103
+ Layer.succeed(AiGatewayTag, aiGateway),
104
+ Layer.effect(AiGatewayModelsTag, Deferred.await(aiGatewayModelsDeferred)),
105
+ Layer.effect(RuntimeBridgeTag, Deferred.await(runtimeBridgeDeferred)),
106
+ )
107
+ const layerWithBridge = Layer.mergeAll(infrastructureLayer, bridgeLayer)
108
+ const fullLayer = Layer.mergeAll(
109
+ layerWithBridge,
110
+ Layer.provide(Layer.mergeAll(EmbeddingCacheLive, FirecrawlLive), layerWithBridge),
111
+ )
93
112
 
94
113
  const managedRuntime = ManagedRuntime.make(fullLayer)
114
+ const runtimeBridge: RuntimeBridge = {
115
+ runPromise: (effect, options) => managedRuntime.runPromise(effect, options),
116
+ runFork: (effect) => managedRuntime.runFork(effect),
117
+ }
118
+ const aiGatewayModels = createAiGatewayModels({
119
+ gateway: aiGateway,
120
+ runtimeConfig,
121
+ runPromise: runtimeBridge.runPromise,
122
+ runFork: runtimeBridge.runFork,
123
+ })
124
+ yield* Deferred.succeed(runtimeBridgeDeferred, runtimeBridge)
125
+ yield* Deferred.succeed(aiGatewayModelsDeferred, aiGatewayModels)
95
126
 
96
127
  return managedRuntime
97
128
  }),
@@ -111,7 +142,10 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
111
142
  // Assign before the async kicks off so concurrent callers observe the in-flight promise.
112
143
  sandboxedWorkerInitPromise = Effect.runPromise(
113
144
  Effect.gen(function* () {
114
- const env = parseWorkerBootstrapEnv(Bun.env)
145
+ const env = yield* Effect.try({
146
+ try: () => parseWorkerBootstrapEnv(Bun.env),
147
+ catch: (error) => toSandboxedWorkerBootstrapError('setup', error),
148
+ })
115
149
  const runtime = yield* Effect.tryPromise({
116
150
  try: () => ensureSandboxedWorkerRuntimeConfigured(),
117
151
  catch: (error) => toSandboxedWorkerBootstrapError('setup', error),
@@ -124,7 +158,7 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
124
158
  yield* Effect.tryPromise({
125
159
  try: () =>
126
160
  connectWithStartupRetry({
127
- connect: () => db.connect(),
161
+ connect: () => runtime.runPromise(db.connect()),
128
162
  label: 'sandboxed worker AI database runtime',
129
163
  logger: serverLogger,
130
164
  }),
@@ -156,7 +190,7 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
156
190
  expectedFingerprint: env.DB_SCHEMA_FINGERPRINT,
157
191
  label: 'sandboxed worker runtime',
158
192
  logger: serverLogger,
159
- connect: () => db.connect(),
193
+ connect: () => runtime.runPromise(db.connect()),
160
194
  }),
161
195
  catch: (error) => toSandboxedWorkerBootstrapError('bootstrap-wait', error),
162
196
  })
@@ -170,3 +204,33 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
170
204
 
171
205
  return sandboxedWorkerInitPromise
172
206
  }
207
+
208
+ /**
209
+ * Dispose the sandboxed worker runtime and clear both module-level caches so
210
+ * a subsequent initialize call rebuilds a fresh runtime. Registered as a
211
+ * SIGTERM/SIGINT handler when the worker process runs as the main entrypoint.
212
+ */
213
+ // @effect-diagnostics-next-line asyncFunction:off -- host-boundary dispose handler returns Promise by design.
214
+ export async function disposeSandboxedWorkerRuntime(): Promise<void> {
215
+ const setupPromise = sandboxedWorkerSetupPromise
216
+ sandboxedWorkerSetupPromise = null
217
+ sandboxedWorkerInitPromise = null
218
+ if (!setupPromise) return
219
+ try {
220
+ const runtime = await setupPromise
221
+ await runtime.dispose()
222
+ } catch (error) {
223
+ serverLogger.warn`Failed to dispose sandboxed worker runtime: ${error}`
224
+ }
225
+ }
226
+
227
+ let sandboxedWorkerShutdownHandlersRegistered = false
228
+ export function registerSandboxedWorkerShutdownHandlers(): void {
229
+ if (sandboxedWorkerShutdownHandlersRegistered) return
230
+ sandboxedWorkerShutdownHandlersRegistered = true
231
+ const handle = () => {
232
+ void disposeSandboxedWorkerRuntime().catch(() => undefined)
233
+ }
234
+ process.once('SIGTERM', handle)
235
+ process.once('SIGINT', handle)
236
+ }
@@ -8,7 +8,7 @@ import { ensureRecordId, recordIdToString } from '../db/record-id'
8
8
  import type { RecordIdInput } from '../db/record-id'
9
9
  import type { SurrealDBService } from '../db/service'
10
10
  import { TABLES } from '../db/tables'
11
- import { effectTryPromise } from '../effect/helpers'
11
+ import { ERROR_TAGS } from '../effect/errors'
12
12
  import { DatabaseServiceTag } from '../effect/services'
13
13
  import type { MemoryConsolidationJob } from '../queues/memory-consolidation.queue'
14
14
  import { QueueJobServiceTag } from '../services/queue-job.service'
@@ -18,27 +18,30 @@ import { initializeSandboxedWorkerRuntime } from './bootstrap'
18
18
  import { toSandboxedWorkerError } from './utils/sandbox-error'
19
19
  import { createTracedWorkerProcessor } from './worker-utils'
20
20
 
21
- class MemoryConsolidationError extends Schema.TaggedErrorClass<MemoryConsolidationError>()('MemoryConsolidationError', {
22
- stage: Schema.Literals([
23
- 'read-memories',
24
- 'read-neighbors',
25
- 'archive-memory',
26
- 'relate-memories',
27
- 'write-history',
28
- 'load-stale',
29
- 'update-stale',
30
- 'load-middle-nodes',
31
- 'read-existing-relation',
32
- 'write-relation',
33
- 'update-middle-node',
34
- 'decay-standard',
35
- 'decay-ephemeral',
36
- 'cleanup-relations',
37
- 'load-scope-ids',
38
- ]),
39
- message: Schema.String,
40
- cause: Schema.Defect,
41
- }) {}
21
+ class MemoryConsolidationError extends Schema.TaggedErrorClass<MemoryConsolidationError>()(
22
+ ERROR_TAGS.MemoryConsolidationError,
23
+ {
24
+ stage: Schema.Literals([
25
+ 'read-memories',
26
+ 'read-neighbors',
27
+ 'archive-memory',
28
+ 'relate-memories',
29
+ 'write-history',
30
+ 'load-stale',
31
+ 'update-stale',
32
+ 'load-middle-nodes',
33
+ 'read-existing-relation',
34
+ 'write-relation',
35
+ 'update-middle-node',
36
+ 'decay-standard',
37
+ 'decay-ephemeral',
38
+ 'cleanup-relations',
39
+ 'load-scope-ids',
40
+ ]),
41
+ message: Schema.String,
42
+ cause: Schema.Defect,
43
+ },
44
+ ) {}
42
45
 
43
46
  function toMemoryConsolidationError(
44
47
  stage: MemoryConsolidationError['stage'],
@@ -47,13 +50,6 @@ function toMemoryConsolidationError(
47
50
  return new MemoryConsolidationError({ stage, message: getErrorMessage(cause), cause })
48
51
  }
49
52
 
50
- function tryMemoryConsolidationEffect<A, R = never>(
51
- stage: MemoryConsolidationError['stage'],
52
- thunk: () => PromiseLike<A> | Effect.Effect<A, unknown, R>,
53
- ): Effect.Effect<A, MemoryConsolidationError, R> {
54
- return effectTryPromise(thunk, (cause) => toMemoryConsolidationError(stage, cause))
55
- }
56
-
57
53
  const runtime = await initializeSandboxedWorkerRuntime()
58
54
 
59
55
  const memoryConsolidationDatabaseService: SurrealDBService = await runtime.runPromise(
@@ -93,8 +89,8 @@ function isContentSubsumed(shorter: string, longer: string): boolean {
93
89
 
94
90
  function deduplicateScopeEffect(scopeId: string) {
95
91
  return Effect.gen(function* () {
96
- const memoryRows = yield* tryMemoryConsolidationEffect('read-memories', () =>
97
- db().query<{
92
+ const memoryRows = yield* db()
93
+ .query<{
98
94
  id: RecordIdInput
99
95
  content: string
100
96
  importance: number
@@ -110,8 +106,8 @@ function deduplicateScopeEffect(scopeId: string) {
110
106
  LIMIT $limit`,
111
107
  { scopeId, limit: MAX_MEMORIES_PER_SCOPE },
112
108
  ),
113
- ),
114
- )
109
+ )
110
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('read-memories', cause)))
115
111
 
116
112
  const memories = memoryRows.map((row) => ({ ...row, id: toMemoryId(row.id) }))
117
113
  if (memories.length < 2) return 0
@@ -123,8 +119,8 @@ function deduplicateScopeEffect(scopeId: string) {
123
119
  if (archived.has(memory.id)) continue
124
120
 
125
121
  const candidateLimit = 20
126
- const neighborStatements = yield* tryMemoryConsolidationEffect('read-neighbors', () =>
127
- db().queryAll<{
122
+ const neighborStatements = yield* db()
123
+ .queryAll<{
128
124
  id: RecordIdInput
129
125
  content: string
130
126
  importance: number
@@ -159,8 +155,8 @@ function deduplicateScopeEffect(scopeId: string) {
159
155
  LIMIT ${candidateLimit}`,
160
156
  { scopeId, memoryId: ensureRecordId(memory.id, TABLES.MEMORY), embedding: memory.embedding },
161
157
  ),
162
- ),
163
- )
158
+ )
159
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('read-neighbors', cause)))
164
160
 
165
161
  const neighbors = (neighborStatements.at(-1) ?? []).map((row) => ({ ...row, id: toMemoryId(row.id) }))
166
162
 
@@ -185,31 +181,31 @@ function deduplicateScopeEffect(scopeId: string) {
185
181
  const winner = keepMemory ? memory : neighbor
186
182
  const loser = keepMemory ? neighbor : memory
187
183
 
188
- yield* tryMemoryConsolidationEffect('archive-memory', () =>
189
- db().query(
184
+ yield* db()
185
+ .query(
190
186
  new BoundQuery(
191
187
  `UPDATE ${MEMORY_TABLE}
192
188
  SET archivedAt = time::now(), validUntil = time::now()
193
189
  WHERE id = $loserId AND archivedAt IS NONE`,
194
190
  { loserId: ensureRecordId(loser.id, TABLES.MEMORY) },
195
191
  ),
196
- ),
197
- )
198
- yield* tryMemoryConsolidationEffect('relate-memories', () =>
199
- db().relate(
192
+ )
193
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('archive-memory', cause)))
194
+ yield* db()
195
+ .relate(
200
196
  ensureRecordId(winner.id, TABLES.MEMORY),
201
197
  MEMORY_RELATION_TABLE,
202
198
  ensureRecordId(loser.id, TABLES.MEMORY),
203
199
  { relationType: RELATION_SUPERSEDES, confidence: 1.0 },
204
- ),
205
- )
206
- yield* tryMemoryConsolidationEffect('write-history', () =>
207
- db().insert<Record<string, unknown>>(MEMORY_HISTORY_TABLE, {
200
+ )
201
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('relate-memories', cause)))
202
+ yield* db()
203
+ .insert<Record<string, unknown>>(MEMORY_HISTORY_TABLE, {
208
204
  memoryId: ensureRecordId(loser.id, TABLES.MEMORY),
209
205
  prevValue: loser.content,
210
206
  event: 'DELETE',
211
- }),
212
- )
207
+ })
208
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('write-history', cause)))
213
209
 
214
210
  archived.add(loser.id)
215
211
  mergeCount++
@@ -224,8 +220,8 @@ function deduplicateScopeEffect(scopeId: string) {
224
220
 
225
221
  function pruneStaleMemoriesEffect() {
226
222
  return Effect.gen(function* () {
227
- const stale = yield* tryMemoryConsolidationEffect('load-stale', () =>
228
- db().query<{ id: RecordIdInput }>(
223
+ const stale = yield* db()
224
+ .query<{ id: RecordIdInput }>(
229
225
  new BoundQuery(
230
226
  `SELECT id FROM ${MEMORY_TABLE}
231
227
  WHERE accessCount = 0
@@ -234,15 +230,15 @@ function pruneStaleMemoriesEffect() {
234
230
  AND importance < 0.5
235
231
  LIMIT 200`,
236
232
  ),
237
- ),
238
- )
233
+ )
234
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('load-stale', cause)))
239
235
 
240
236
  if (stale.length === 0) return 0
241
237
 
242
238
  const staleIds = stale.map((row) => ensureRecordId(row.id, TABLES.MEMORY))
243
- yield* tryMemoryConsolidationEffect('update-stale', () =>
244
- db().updateWhere(MEMORY_TABLE, inside('id', staleIds), { archivedAt: nowDate() }),
245
- )
239
+ yield* db()
240
+ .updateWhere(MEMORY_TABLE, inside('id', staleIds), { archivedAt: nowDate() })
241
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('update-stale', cause)))
246
242
 
247
243
  return stale.length
248
244
  })
@@ -250,8 +246,8 @@ function pruneStaleMemoriesEffect() {
250
246
 
251
247
  function collapseSupersededChainEffect() {
252
248
  return Effect.gen(function* () {
253
- const middleNodes = yield* tryMemoryConsolidationEffect('load-middle-nodes', () =>
254
- db().query<{ middleId: RecordIdInput; predecessors: RecordIdInput[]; successors: RecordIdInput[] }>(
249
+ const middleNodes = yield* db()
250
+ .query<{ middleId: RecordIdInput; predecessors: RecordIdInput[]; successors: RecordIdInput[] }>(
255
251
  new BoundQuery(
256
252
  `SELECT
257
253
  id AS middleId,
@@ -263,8 +259,8 @@ function collapseSupersededChainEffect() {
263
259
  AND count(<-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
264
260
  LIMIT ${MEMORY.MAX_KNN_LIMIT}`,
265
261
  ),
266
- ),
267
- )
262
+ )
263
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('load-middle-nodes', cause)))
268
264
 
269
265
  let collapsed = 0
270
266
 
@@ -273,33 +269,28 @@ function collapseSupersededChainEffect() {
273
269
  for (const succId of node.successors) {
274
270
  const predRef = ensureRecordId(predId, TABLES.MEMORY)
275
271
  const succRef = ensureRecordId(succId, TABLES.MEMORY)
276
- const existing = yield* tryMemoryConsolidationEffect('read-existing-relation', () =>
277
- db().query<{ id: RecordIdInput }>(
272
+ const existing = yield* db()
273
+ .query<{ id: RecordIdInput }>(
278
274
  new BoundQuery(
279
275
  `SELECT id FROM ${MEMORY_RELATION_TABLE}
280
276
  WHERE in = $predId AND out = $succId AND relationType = '${RELATION_SUPERSEDES}'
281
277
  LIMIT 1`,
282
278
  { predId: predRef, succId: succRef },
283
279
  ),
284
- ),
285
- )
280
+ )
281
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('read-existing-relation', cause)))
286
282
 
287
283
  if (existing.length === 0) {
288
- yield* tryMemoryConsolidationEffect('write-relation', () =>
289
- db().relate(predRef, MEMORY_RELATION_TABLE, succRef, {
290
- relationType: RELATION_SUPERSEDES,
291
- confidence: 1.0,
292
- }),
293
- )
284
+ yield* db()
285
+ .relate(predRef, MEMORY_RELATION_TABLE, succRef, { relationType: RELATION_SUPERSEDES, confidence: 1.0 })
286
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('write-relation', cause)))
294
287
  }
295
288
  }
296
289
  }
297
290
 
298
- yield* tryMemoryConsolidationEffect('update-middle-node', () =>
299
- db().updateWhere(MEMORY_TABLE, eq('id', ensureRecordId(node.middleId, TABLES.MEMORY)), {
300
- archivedAt: nowDate(),
301
- }),
302
- )
291
+ yield* db()
292
+ .updateWhere(MEMORY_TABLE, eq('id', ensureRecordId(node.middleId, TABLES.MEMORY)), { archivedAt: nowDate() })
293
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('update-middle-node', cause)))
303
294
 
304
295
  collapsed++
305
296
  }
@@ -311,8 +302,8 @@ function collapseSupersededChainEffect() {
311
302
  function decayImportanceEffect() {
312
303
  return Effect.gen(function* () {
313
304
  const [standardResult, ephemeralResult] = yield* Effect.all([
314
- tryMemoryConsolidationEffect('decay-standard', () =>
315
- db().query<{ count: number }>(
305
+ db()
306
+ .query<{ count: number }>(
316
307
  new BoundQuery(
317
308
  `UPDATE ${MEMORY_TABLE}
318
309
  SET importance = math::max([0.1, importance * 0.95])
@@ -323,10 +314,10 @@ function decayImportanceEffect() {
323
314
  AND (durability = 'standard' OR durability IS NONE)
324
315
  RETURN count() AS count`,
325
316
  ),
326
- ),
327
- ),
328
- tryMemoryConsolidationEffect('decay-ephemeral', () =>
329
- db().query<{ count: number }>(
317
+ )
318
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('decay-standard', cause))),
319
+ db()
320
+ .query<{ count: number }>(
330
321
  new BoundQuery(
331
322
  `UPDATE ${MEMORY_TABLE}
332
323
  SET importance = math::max([0.1, importance * 0.85])
@@ -337,8 +328,8 @@ function decayImportanceEffect() {
337
328
  AND durability = 'ephemeral'
338
329
  RETURN count() AS count`,
339
330
  ),
340
- ),
341
- ),
331
+ )
332
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('decay-ephemeral', cause))),
342
333
  ])
343
334
 
344
335
  return (standardResult[0]?.count ?? 0) + (ephemeralResult[0]?.count ?? 0)
@@ -347,15 +338,15 @@ function decayImportanceEffect() {
347
338
 
348
339
  function cleanupOrphanedRelationsEffect() {
349
340
  return Effect.gen(function* () {
350
- const result = yield* tryMemoryConsolidationEffect('cleanup-relations', () =>
351
- db().query<{ count: number }>(
341
+ const result = yield* db()
342
+ .query<{ count: number }>(
352
343
  new BoundQuery(
353
344
  `DELETE ${MEMORY_RELATION_TABLE}
354
345
  WHERE in.archivedAt IS NOT NONE OR out.archivedAt IS NOT NONE
355
346
  RETURN count() AS count`,
356
347
  ),
357
- ),
358
- )
348
+ )
349
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('cleanup-relations', cause)))
359
350
  return result[0]?.count ?? 0
360
351
  })
361
352
  }
@@ -369,11 +360,11 @@ const handler = (job: SandboxedJob<MemoryConsolidationJob>) =>
369
360
  if (targetScope) {
370
361
  totalMerged = yield* deduplicateScopeEffect(targetScope)
371
362
  } else {
372
- const scopeIds = yield* tryMemoryConsolidationEffect('load-scope-ids', () =>
373
- db().query<string>(
363
+ const scopeIds = yield* db()
364
+ .query<string>(
374
365
  new BoundQuery(`SELECT VALUE scopeId FROM ${MEMORY_TABLE} WHERE archivedAt IS NONE GROUP BY scopeId`),
375
- ),
376
- )
366
+ )
367
+ .pipe(Effect.mapError((cause) => toMemoryConsolidationError('load-scope-ids', cause)))
377
368
 
378
369
  const results = yield* Effect.forEach(scopeIds, deduplicateScopeEffect, { concurrency: 2 })
379
370
  totalMerged = results.reduce((a, b) => a + b, 0)
@@ -1,9 +1,9 @@
1
1
  import type { SandboxedJob } from 'bullmq'
2
- import { Effect } from 'effect'
2
+ import { Cause, Effect } from 'effect'
3
3
  import type { Context } from 'effect'
4
4
 
5
+ import { AiGatewayModelsTag } from '../ai-gateway/ai-gateway'
5
6
  import { serverLogger } from '../config/logger'
6
- import { effectTryPromise } from '../effect/helpers'
7
7
  import {
8
8
  AgentConfigServiceTag,
9
9
  DatabaseServiceTag,
@@ -27,9 +27,11 @@ import { createTracedWorkerProcessor } from './worker-utils'
27
27
  const runtime = await initializeSandboxedWorkerRuntime()
28
28
  const resolve = <I, T>(tag: Context.Key<I, T>): Promise<T> => runtime.runPromise(Effect.service(tag))
29
29
  const agentConfig = await resolve(AgentConfigServiceTag)
30
+ const aiGatewayModels = await resolve(AiGatewayModelsTag)
30
31
  const queues = await resolve(LotaQueuesServiceTag)
31
32
  const regularChatDigestServices: RegularChatDigestServices = {
32
33
  agentConfig,
34
+ aiGatewayModels,
33
35
  databaseService: await resolve(DatabaseServiceTag),
34
36
  memoryService: await resolve(MemoryServiceTag),
35
37
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
@@ -39,12 +41,14 @@ const regularChatDigestServices: RegularChatDigestServices = {
39
41
  const workerRuntimeConfig = await resolve(RuntimeConfigServiceTag)
40
42
  const skillExtractionServices: SkillExtractionServices = {
41
43
  agentConfig,
44
+ aiGatewayModels,
42
45
  databaseService: await resolve(DatabaseServiceTag),
43
46
  learnedSkillService: await resolve(LearnedSkillServiceTag),
44
47
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
45
48
  runtimeAdapters: await resolve(RuntimeAdaptersServiceTag),
46
49
  embeddingModel: workerRuntimeConfig.aiGateway.embeddingModel,
47
50
  openRouterApiKey: workerRuntimeConfig.aiGateway.openRouterApiKey,
51
+ runPromise: (effect) => runtime.runPromise(effect),
48
52
  }
49
53
  const organizationLearningQueueJobService = await resolve(QueueJobServiceTag)
50
54
 
@@ -55,11 +59,17 @@ const handler = (job: SandboxedJob<OrganizationLearningJob>) =>
55
59
  Effect.gen(function* () {
56
60
  if (job.data.kind === 'regular-chat-memory-digest') {
57
61
  const digestJob = job.data
58
- return yield* effectTryPromise(() => runRegularChatMemoryDigest(digestJob, regularChatDigestServices))
62
+ return yield* Effect.tryPromise({
63
+ try: () => runRegularChatMemoryDigest(digestJob, regularChatDigestServices),
64
+ catch: (cause) => new Cause.UnknownError(cause),
65
+ })
59
66
  }
60
67
 
61
68
  const skillJob = job.data
62
- return yield* effectTryPromise(() => runSkillExtraction(skillJob, skillExtractionServices))
69
+ return yield* Effect.tryPromise({
70
+ try: () => runSkillExtraction(skillJob, skillExtractionServices),
71
+ catch: (cause) => new Cause.UnknownError(cause),
72
+ })
63
73
  }).pipe(
64
74
  Effect.catch((error) =>
65
75
  Effect.gen(function* () {