@lota-sdk/core 0.4.13 → 0.4.15

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/config/constants.ts +1 -1
  8. package/src/create-runtime.ts +259 -200
  9. package/src/db/cursor-pagination.ts +2 -9
  10. package/src/db/memory-store.ts +194 -175
  11. package/src/db/memory.ts +125 -71
  12. package/src/db/schema-fingerprint.ts +5 -4
  13. package/src/db/service-normalization.ts +4 -3
  14. package/src/db/service.ts +3 -2
  15. package/src/db/startup.ts +15 -16
  16. package/src/effect/errors.ts +161 -21
  17. package/src/effect/index.ts +0 -1
  18. package/src/embeddings/provider.ts +15 -7
  19. package/src/queues/autonomous-job.queue.ts +10 -22
  20. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  21. package/src/queues/document-processor.queue.ts +13 -4
  22. package/src/queues/memory-consolidation.queue.ts +26 -14
  23. package/src/queues/plan-agent-heartbeat.queue.ts +48 -31
  24. package/src/queues/plan-scheduler.queue.ts +37 -15
  25. package/src/queues/queue-factory.ts +59 -35
  26. package/src/queues/standalone-worker.ts +3 -2
  27. package/src/redis/connection.ts +10 -3
  28. package/src/redis/org-memory-lock.ts +1 -1
  29. package/src/redis/redis-lease-lock.ts +5 -5
  30. package/src/redis/stream-context.ts +1 -1
  31. package/src/runtime/chat-message.ts +64 -1
  32. package/src/runtime/chat-run-orchestration.ts +33 -20
  33. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  34. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  35. package/src/runtime/domain-layer.ts +19 -13
  36. package/src/runtime/execution-plan.ts +7 -3
  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 +22 -24
  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 +83 -92
  103. package/src/services/thread/thread-turn.ts +18 -16
  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 +11 -7
  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
@@ -10,7 +10,6 @@ import type { RecordIdRef } from '../../db/record-id'
10
10
  import type { SurrealDBService } from '../../db/service'
11
11
  import { TABLES } from '../../db/tables'
12
12
  import { BadRequestError, ServiceError } from '../../effect/errors'
13
- import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
14
13
  import {
15
14
  AgentConfigServiceTag,
16
15
  DatabaseServiceTag,
@@ -20,7 +19,7 @@ import {
20
19
  import type { RedisConnectionManager } from '../../redis/connection'
21
20
  import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
22
21
  import { toIsoDateTimeString } from '../../utils/date-time'
23
- import { BackgroundWorkService } from '../background-work.service'
22
+ import { BackgroundWorkServiceTag } from '../background-work.service'
24
23
  import { ChatRunRegistryTag } from '../chat-run-registry.service'
25
24
  import { ContextCompactionServiceTag } from '../context-compaction.service'
26
25
  import type { makeContextCompactionService } from '../context-compaction.service'
@@ -63,8 +62,6 @@ function toPublicThread(thread: NormalizedThread): PublicThread {
63
62
 
64
63
  type ThreadServiceError = ServiceError | BadRequestError
65
64
 
66
- const effectTryPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
67
-
68
65
  type ChatRunRegistry = Context.Service.Shape<typeof ChatRunRegistryTag>
69
66
 
70
67
  type CompactionCoordination = Context.Service.Shape<typeof CompactionCoordinationTag>
@@ -78,7 +75,7 @@ interface ThreadServiceDeps {
78
75
  threadMessageService: ReturnType<typeof makeThreadMessageService>
79
76
  contextCompactionService: ReturnType<typeof makeContextCompactionService>
80
77
  compactionCoordination: CompactionCoordination
81
- background: Context.Service.Shape<typeof BackgroundWorkService>
78
+ background: Context.Service.Shape<typeof BackgroundWorkServiceTag>
82
79
  }
83
80
 
84
81
  export function makeThreadService(deps: ThreadServiceDeps) {
@@ -109,10 +106,11 @@ export function makeThreadService(deps: ThreadServiceDeps) {
109
106
  return Effect.succeed(true)
110
107
  }
111
108
 
112
- return effectTryPromise(
113
- () => activeRun.hasActiveRunLease(ensureRecordId(thread.id, TABLES.THREAD)),
114
- 'Failed to check active thread run lease.',
115
- )
109
+ return activeRun
110
+ .hasActiveRunLease(ensureRecordId(thread.id, TABLES.THREAD))
111
+ .pipe(
112
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to check active thread run lease.', cause })),
113
+ )
116
114
  }
117
115
 
118
116
  function toNormalizedThread(
@@ -174,68 +172,59 @@ export function makeThreadService(deps: ThreadServiceDeps) {
174
172
  background: deps.background,
175
173
  })
176
174
 
177
- function getById(threadId: RecordIdRef) {
178
- return effectTryPromise(() => threadStore.getById(threadId), 'Failed to load thread.')
179
- }
175
+ const getById = Effect.fn('ThreadService.getById')(function* (threadId: RecordIdRef) {
176
+ return yield* threadStore
177
+ .getById(threadId)
178
+ .pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to load thread.', cause })))
179
+ })
180
180
 
181
- function getThread(threadId: RecordIdRef) {
182
- return Effect.gen(function* () {
183
- const thread = yield* getById(threadId)
184
- return yield* toNormalizedThread(thread)
185
- })
186
- }
181
+ const getThread = Effect.fn('ThreadService.getThread')(function* (threadId: RecordIdRef) {
182
+ const thread = yield* getById(threadId)
183
+ return yield* toNormalizedThread(thread)
184
+ })
187
185
 
188
- function updateTitle(threadId: RecordIdRef, title: string) {
189
- return Effect.gen(function* () {
190
- const existing = yield* getById(threadId)
191
- yield* assertMutableThreadEffect(existing)
192
- const thread = yield* effectTryPromise(
193
- () => threadStore.update(threadId, { title, nameGenerated: true }),
194
- 'Failed to update thread title.',
195
- )
196
- return yield* toNormalizedThread(thread)
197
- })
198
- }
186
+ const updateTitle = Effect.fn('ThreadService.updateTitle')(function* (threadId: RecordIdRef, title: string) {
187
+ const existing = yield* getById(threadId)
188
+ yield* assertMutableThreadEffect(existing)
189
+ const thread = yield* threadStore
190
+ .update(threadId, { title, nameGenerated: true })
191
+ .pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread title.', cause })))
192
+ return yield* toNormalizedThread(thread)
193
+ })
199
194
 
200
- function updateStatus(threadId: RecordIdRef, status: string) {
201
- return Effect.gen(function* () {
202
- const parsedStatus = sdkThreadStatusSchema.safeParse(status)
203
- if (!parsedStatus.success) {
204
- return yield* new BadRequestError({ message: `Invalid thread status: ${status}` })
205
- }
206
- const existing = yield* getById(threadId)
207
- yield* assertMutableThreadEffect(existing)
208
- const thread = yield* effectTryPromise(
209
- () => threadStore.update(threadId, { status: parsedStatus.data }),
210
- 'Failed to update thread status.',
211
- )
212
- return yield* toNormalizedThread(thread)
213
- })
214
- }
195
+ const updateStatus = Effect.fn('ThreadService.updateStatus')(function* (threadId: RecordIdRef, status: string) {
196
+ const parsedStatus = sdkThreadStatusSchema.safeParse(status)
197
+ if (!parsedStatus.success) {
198
+ return yield* new BadRequestError({ message: `Invalid thread status: ${status}` })
199
+ }
200
+ const existing = yield* getById(threadId)
201
+ yield* assertMutableThreadEffect(existing)
202
+ const thread = yield* threadStore
203
+ .update(threadId, { status: parsedStatus.data })
204
+ .pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread status.', cause })))
205
+ return yield* toNormalizedThread(thread)
206
+ })
215
207
 
216
- function setCompacting(threadId: RecordIdRef, value: boolean) {
208
+ const setCompacting = Effect.fn('ThreadService.setCompacting')(function* (threadId: RecordIdRef, value: boolean) {
217
209
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
218
210
  const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
219
- return Effect.asVoid(
220
- effectTryPromise(
221
- () => deps.db.query<unknown>(surql`UPDATE ONLY ${threadRef} SET isCompacting = ${value}`),
222
- 'Failed to update thread compaction flag.',
223
- ).pipe(Effect.tap(() => deps.compactionCoordination.signal(threadIdString, value))),
224
- )
225
- }
226
-
227
- function clearThread(threadId: RecordIdRef) {
228
- return Effect.gen(function* () {
229
- const existing = yield* getById(threadId)
230
- yield* assertMutableThreadEffect(existing)
231
- const threadRef = ensureRecordId(threadId, TABLES.THREAD)
232
- yield* effectTryPromise(
233
- () => deps.db.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef }),
234
- 'Failed to delete thread messages.',
211
+ yield* deps.db
212
+ .query<unknown>(surql`UPDATE ONLY ${threadRef} SET isCompacting = ${value}`)
213
+ .pipe(
214
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread compaction flag.', cause })),
235
215
  )
236
- yield* effectTryPromise(
237
- () =>
238
- deps.db.query<unknown>(surql`
216
+ yield* deps.compactionCoordination.signal(threadIdString, value)
217
+ })
218
+
219
+ const clearThread = Effect.fn('ThreadService.clearThread')(function* (threadId: RecordIdRef) {
220
+ const existing = yield* getById(threadId)
221
+ yield* assertMutableThreadEffect(existing)
222
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
223
+ yield* deps.db
224
+ .deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef })
225
+ .pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to delete thread messages.', cause })))
226
+ yield* deps.db
227
+ .query<unknown>(surql`
239
228
  UPDATE ONLY ${threadRef}
240
229
  SET turnCount = 0,
241
230
  compactionSummary = NONE,
@@ -243,68 +232,102 @@ export function makeThreadService(deps: ThreadServiceDeps) {
243
232
  activeRunId = NONE,
244
233
  activeStreamId = NONE,
245
234
  isCompacting = false
246
- `),
247
- 'Failed to reset thread state.',
248
- )
249
- })
250
- }
235
+ `)
236
+ .pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to reset thread state.', cause })))
237
+ })
251
238
 
252
- function deleteThread(threadId: RecordIdRef) {
253
- return Effect.gen(function* () {
254
- const existing = yield* getById(threadId)
255
- yield* assertMutableThreadEffect(existing)
256
- yield* effectTryPromise(() => threadStore.deleteById(threadId), 'Failed to delete thread.')
257
- })
258
- }
239
+ const deleteThread = Effect.fn('ThreadService.deleteThread')(function* (threadId: RecordIdRef) {
240
+ const existing = yield* getById(threadId)
241
+ yield* assertMutableThreadEffect(existing)
242
+ yield* threadStore
243
+ .deleteById(threadId)
244
+ .pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to delete thread.', cause })))
245
+ })
259
246
 
260
247
  function incrementTurnCount(threadId: RecordIdRef) {
261
248
  const threadRef = ensureRecordId(threadId, TABLES.THREAD)
262
249
  return Effect.gen(function* () {
263
- const result = yield* effectTryPromise(
264
- () =>
265
- deps.db.query<{ turnCount: number }>(
266
- surql`
250
+ const result = yield* deps.db
251
+ .query<{ turnCount: number }>(
252
+ surql`
267
253
  UPDATE ONLY ${threadRef}
268
254
  SET turnCount += 1
269
255
  RETURN turnCount
270
256
  `,
271
- ),
272
- 'Failed to increment thread turn count.',
273
- )
257
+ )
258
+ .pipe(
259
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to increment thread turn count.', cause })),
260
+ )
274
261
  return result[0].turnCount
275
262
  })
276
263
  }
277
264
 
278
265
  return {
279
266
  findById: (...args: Parameters<typeof threadStore.findById>) =>
280
- effectTryPromise(() => threadStore.findById(...args), 'Failed to find thread.'),
267
+ threadStore.findById(...args).pipe(
268
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to find thread.', cause })),
269
+ Effect.withSpan('ThreadService.findById'),
270
+ ),
281
271
  getById,
282
272
  findAll: (...args: Parameters<typeof threadStore.findAll>) =>
283
- effectTryPromise(() => threadStore.findAll(...args), 'Failed to find all threads.'),
273
+ threadStore.findAll(...args).pipe(
274
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to find all threads.', cause })),
275
+ Effect.withSpan('ThreadService.findAll'),
276
+ ),
284
277
  create: (...args: Parameters<typeof threadStore.create>) =>
285
- effectTryPromise(() => threadStore.create(...args), 'Failed to create thread.'),
278
+ threadStore.create(...args).pipe(
279
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to create thread.', cause })),
280
+ Effect.withSpan('ThreadService.create'),
281
+ ),
286
282
  update: (...args: Parameters<typeof threadStore.update>) =>
287
- effectTryPromise(() => threadStore.update(...args), 'Failed to update thread.'),
283
+ threadStore.update(...args).pipe(
284
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread.', cause })),
285
+ Effect.withSpan('ThreadService.update'),
286
+ ),
288
287
  delete: (...args: Parameters<typeof threadStore.deleteById>) =>
289
- effectTryPromise(() => threadStore.deleteById(...args), 'Failed to delete thread.'),
288
+ threadStore.deleteById(...args).pipe(
289
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to delete thread.', cause })),
290
+ Effect.withSpan('ThreadService.delete'),
291
+ ),
290
292
  getOrCreateDefault: (...args: Parameters<typeof bootstrap.getOrCreateDefault>) =>
291
- effectTryPromise(() => bootstrap.getOrCreateDefault(...args), 'Failed to get or create default thread.'),
293
+ bootstrap.getOrCreateDefault(...args).pipe(
294
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to get or create default thread.', cause })),
295
+ Effect.withSpan('ThreadService.getOrCreateDefault'),
296
+ ),
292
297
  getOrCreateThread: (...args: Parameters<typeof bootstrap.getOrCreateThread>) =>
293
- effectTryPromise(() => bootstrap.getOrCreateThread(...args), 'Failed to get or create thread.'),
298
+ bootstrap.getOrCreateThread(...args).pipe(
299
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to get or create thread.', cause })),
300
+ Effect.withSpan('ThreadService.getOrCreateThread'),
301
+ ),
294
302
  createThread: (...args: Parameters<typeof bootstrap.createThread>) =>
295
- effectTryPromise(() => bootstrap.createThread(...args), 'Failed to create thread.'),
303
+ bootstrap.createThread(...args).pipe(
304
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to create thread.', cause })),
305
+ Effect.withSpan('ThreadService.createThread'),
306
+ ),
296
307
  ensureBootstrapThreads: (...args: Parameters<typeof bootstrap.ensureBootstrapThreads>) =>
297
- effectTryPromise(() => bootstrap.ensureBootstrapThreads(...args), 'Failed to ensure bootstrap threads.'),
308
+ bootstrap.ensureBootstrapThreads(...args).pipe(
309
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to ensure bootstrap threads.', cause })),
310
+ Effect.withSpan('ThreadService.ensureBootstrapThreads'),
311
+ ),
298
312
  listThreads: (...args: Parameters<typeof listing.listThreads>) =>
299
- effectTryPromise(() => listing.listThreads(...args), 'Failed to list threads.'),
313
+ listing.listThreads(...args).pipe(
314
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to list threads.', cause })),
315
+ Effect.withSpan('ThreadService.listThreads'),
316
+ ),
300
317
  listOrganizationThreads: (...args: Parameters<typeof listing.listOrganizationThreads>) =>
301
- effectTryPromise(() => listing.listOrganizationThreads(...args), 'Failed to list organization threads.'),
318
+ listing.listOrganizationThreads(...args).pipe(
319
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to list organization threads.', cause })),
320
+ Effect.withSpan('ThreadService.listOrganizationThreads'),
321
+ ),
302
322
  getThread,
303
323
  updateTitle,
304
324
  updateStatus,
305
325
  setActiveTurn: activeRun.setActiveTurn,
306
326
  getActiveTurn: (...args: Parameters<typeof activeRun.getActiveTurn>) =>
307
- effectTryPromise(() => activeRun.getActiveTurn(...args), 'Failed to get active turn.'),
327
+ activeRun.getActiveTurn(...args).pipe(
328
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to get active turn.', cause })),
329
+ Effect.withSpan('ThreadService.getActiveTurn'),
330
+ ),
308
331
  getActiveRunId: activeRun.getActiveRunId,
309
332
  hasActiveRunLease: activeRun.hasActiveRunLease,
310
333
  withActiveRunLease: activeRun.withActiveRunLease,
@@ -312,16 +335,28 @@ export function makeThreadService(deps: ThreadServiceDeps) {
312
335
  clearActiveTurn: activeRun.clearActiveTurn,
313
336
  clearStaleActiveRunIfMissingFromRegistry: activeRun.clearStaleActiveRunIfMissingFromRegistry,
314
337
  stopActiveRun: (...args: Parameters<typeof activeRun.stopActiveRun>) =>
315
- effectTryPromise(() => activeRun.stopActiveRun(...args), 'Failed to stop active run.'),
338
+ activeRun.stopActiveRun(...args).pipe(
339
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to stop active run.', cause })),
340
+ Effect.withSpan('ThreadService.stopActiveRun'),
341
+ ),
316
342
  setCompacting,
317
343
  appendMemoryBlock: (...args: Parameters<typeof memory.appendMemoryBlock>) =>
318
- effectTryPromise(() => memory.appendMemoryBlock(...args), 'Failed to append memory block.'),
344
+ memory.appendMemoryBlock(...args).pipe(
345
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to append memory block.', cause })),
346
+ Effect.withSpan('ThreadService.appendMemoryBlock'),
347
+ ),
319
348
  compactMemoryBlock: (...args: Parameters<typeof memory.compactMemoryBlock>) =>
320
- effectTryPromise(() => memory.compactMemoryBlock(...args), 'Failed to compact memory block.'),
349
+ memory.compactMemoryBlock(...args).pipe(
350
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to compact memory block.', cause })),
351
+ Effect.withSpan('ThreadService.compactMemoryBlock'),
352
+ ),
321
353
  clearThread,
322
354
  deleteThread,
323
355
  listRecentThreads: (...args: Parameters<typeof listing.listRecentThreads>) =>
324
- effectTryPromise(() => listing.listRecentThreads(...args), 'Failed to list recent threads.'),
356
+ listing.listRecentThreads(...args).pipe(
357
+ Effect.mapError((cause) => new ServiceError({ message: 'Failed to list recent threads.', cause })),
358
+ Effect.withSpan('ThreadService.listRecentThreads'),
359
+ ),
325
360
  formatMemoryBlockForPrompt,
326
361
  toPublicThread,
327
362
  incrementTurnCount,
@@ -343,7 +378,7 @@ export const ThreadServiceLive = Layer.effect(
343
378
  const threadMessageService = yield* ThreadMessageServiceTag
344
379
  const contextCompactionService = yield* ContextCompactionServiceTag
345
380
  const compactionCoordination = yield* CompactionCoordinationTag
346
- const background = yield* BackgroundWorkService
381
+ const background = yield* BackgroundWorkServiceTag
347
382
  return makeThreadService({
348
383
  agentConfig,
349
384
  threadBootstrapConfig,
@@ -6,7 +6,7 @@ import { ensureRecordId, recordIdToString } from '../db/record-id'
6
6
  import type { RecordIdInput } from '../db/record-id'
7
7
  import type { SurrealDBService } from '../db/service'
8
8
  import { TABLES } from '../db/tables'
9
- import { NotFoundError } from '../effect/errors'
9
+ import { ERROR_TAGS, NotFoundError } from '../effect/errors'
10
10
  import { DatabaseServiceTag } from '../effect/services'
11
11
  import { toIsoDateTimeString } from '../utils/date-time'
12
12
 
@@ -28,7 +28,7 @@ function userNotFoundError(userId: RecordIdInput): NotFoundError {
28
28
  })
29
29
  }
30
30
 
31
- class UserServiceError extends Schema.TaggedErrorClass<UserServiceError>()('UserServiceError', {
31
+ class UserServiceError extends Schema.TaggedErrorClass<UserServiceError>()(ERROR_TAGS.UserServiceError, {
32
32
  operation: Schema.String,
33
33
  cause: Schema.Defect,
34
34
  }) {}
@@ -38,59 +38,56 @@ function toUserServiceError(operation: string, cause: unknown): UserServiceError
38
38
  }
39
39
 
40
40
  export function makeUserService(db: SurrealDBService) {
41
- function upsertUserEffect(params: { id: RecordIdInput; name: string; email: string }) {
41
+ const upsertUser = Effect.fn('UserService.upsertUser')(function* (params: {
42
+ id: RecordIdInput
43
+ name: string
44
+ email: string
45
+ }) {
42
46
  const userRef = ensureRecordId(params.id, TABLES.USER)
43
- return db.upsert(TABLES.USER, userRef, { name: params.name, email: params.email }, sdkUserRecordSchema).pipe(
44
- Effect.mapError((cause) => toUserServiceError('upsertUser', cause)),
45
- Effect.map(toPublic),
46
- )
47
- }
47
+ const record = yield* db
48
+ .upsert(TABLES.USER, userRef, { name: params.name, email: params.email }, sdkUserRecordSchema)
49
+ .pipe(Effect.mapError((cause) => toUserServiceError('upsertUser', cause)))
50
+ return toPublic(record)
51
+ })
48
52
 
49
- function getUserEffect(userId: RecordIdInput) {
50
- return db.findOne(TABLES.USER, { id: ensureRecordId(userId, TABLES.USER) }, sdkUserRecordSchema).pipe(
51
- Effect.mapError((cause) => toUserServiceError('getUser', cause)),
52
- Effect.flatMap((record) => (record ? Effect.succeed(record) : Effect.fail(userNotFoundError(userId)))),
53
- Effect.map(toPublic),
54
- )
55
- }
53
+ const getUser = Effect.fn('UserService.getUser')(function* (userId: RecordIdInput) {
54
+ const record = yield* db
55
+ .findOne(TABLES.USER, { id: ensureRecordId(userId, TABLES.USER) }, sdkUserRecordSchema)
56
+ .pipe(Effect.mapError((cause) => toUserServiceError('getUser', cause)))
57
+ if (!record) return yield* userNotFoundError(userId)
58
+ return toPublic(record)
59
+ })
56
60
 
57
- function listUsersEffect() {
58
- return db.findMany(TABLES.USER, {}, sdkUserRecordSchema, { orderBy: 'createdAt', orderDir: 'ASC' }).pipe(
59
- Effect.mapError((cause) => toUserServiceError('listUsers', cause)),
60
- Effect.map((records) => records.map(toPublic)),
61
- )
62
- }
61
+ const listUsers = Effect.fn('UserService.listUsers')(function* () {
62
+ const records = yield* db
63
+ .findMany(TABLES.USER, {}, sdkUserRecordSchema, { orderBy: 'createdAt', orderDir: 'ASC' })
64
+ .pipe(Effect.mapError((cause) => toUserServiceError('listUsers', cause)))
65
+ return records.map(toPublic)
66
+ })
63
67
 
64
- function updateUserEffect(userId: RecordIdInput, params: { name?: string; email?: string }) {
65
- return db.update(TABLES.USER, ensureRecordId(userId, TABLES.USER), params, sdkUserRecordSchema).pipe(
66
- Effect.mapError((cause) => toUserServiceError('updateUser', cause)),
67
- Effect.flatMap((updated) => (updated ? Effect.succeed(updated) : Effect.fail(userNotFoundError(userId)))),
68
- Effect.map(toPublic),
69
- )
70
- }
68
+ const updateUser = Effect.fn('UserService.updateUser')(function* (
69
+ userId: RecordIdInput,
70
+ params: { name?: string; email?: string },
71
+ ) {
72
+ const updated = yield* db
73
+ .update(TABLES.USER, ensureRecordId(userId, TABLES.USER), params, sdkUserRecordSchema)
74
+ .pipe(Effect.mapError((cause) => toUserServiceError('updateUser', cause)))
75
+ if (!updated) return yield* userNotFoundError(userId)
76
+ return toPublic(updated)
77
+ })
71
78
 
72
- function deleteUserEffect(userId: RecordIdInput) {
79
+ const deleteUser = Effect.fn('UserService.deleteUser')(function* (userId: RecordIdInput) {
73
80
  const userRef = ensureRecordId(userId, TABLES.USER)
74
- return Effect.gen(function* () {
75
- yield* db
76
- .deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
77
- .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteMemberships', cause)))
78
- const deleted = yield* db
79
- .deleteById(TABLES.USER, userRef)
80
- .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteUser', cause)))
81
- if (!deleted) {
82
- return yield* userNotFoundError(userId)
83
- }
84
- })
85
- }
81
+ yield* db
82
+ .deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
83
+ .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteMemberships', cause)))
84
+ const deleted = yield* db
85
+ .deleteById(TABLES.USER, userRef)
86
+ .pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteUser', cause)))
87
+ if (!deleted) return yield* userNotFoundError(userId)
88
+ })
86
89
 
87
- return {
88
- upsertUser: upsertUserEffect,
89
- getUser: getUserEffect,
90
- listUsers: listUsersEffect,
91
- updateUser: updateUserEffect,
92
- deleteUser: deleteUserEffect,
93
- }
90
+ return { upsertUser, getUser, listUsers, updateUser, deleteUser }
94
91
  }
95
92
 
96
93
  export class UserServiceTag extends Context.Service<UserServiceTag, ReturnType<typeof makeUserService>>()(
@@ -3,9 +3,11 @@ import { Schema, Effect } from 'effect'
3
3
  import mammoth from 'mammoth'
4
4
  import { PDFParse } from 'pdf-parse'
5
5
 
6
+ import { ERROR_TAGS } from '../effect/errors'
7
+
6
8
  const READ_FILE_PARTS_CHARS_PER_PAGE = 3500
7
9
 
8
- class AttachmentParserError extends Schema.TaggedErrorClass<AttachmentParserError>()('AttachmentParserError', {
10
+ class AttachmentParserError extends Schema.TaggedErrorClass<AttachmentParserError>()(ERROR_TAGS.AttachmentParserError, {
9
11
  message: Schema.String,
10
12
  cause: Schema.Defect,
11
13
  }) {}
@@ -67,7 +69,9 @@ export function extractPdfPages(file: File): Promise<string[]> {
67
69
  }).pipe(
68
70
  Effect.ensuring(
69
71
  tryAttachmentPromise('Failed to destroy PDF parser.', () => parser.destroy()).pipe(
70
- Effect.orDie,
72
+ Effect.catchTag(ERROR_TAGS.AttachmentParserError, (error) =>
73
+ Effect.logWarning(`Attachment parser cleanup failed: ${error.message}`),
74
+ ),
71
75
  Effect.asVoid,
72
76
  ),
73
77
  ),
@@ -3,7 +3,7 @@ import { S3Client } from 'bun'
3
3
  import { Context, Schema, Effect, Layer } from 'effect'
4
4
 
5
5
  import { serverLogger } from '../config/logger'
6
- import { BadRequestError, ForbiddenError } from '../effect/errors'
6
+ import { ERROR_TAGS, BadRequestError, ForbiddenError } from '../effect/errors'
7
7
  import { RuntimeConfigServiceTag } from '../effect/services'
8
8
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
9
9
  import { getErrorMessage } from '../utils/errors'
@@ -36,11 +36,10 @@ export type UploadedThreadAttachment = {
36
36
 
37
37
  type AttachmentContextCandidate = { storageKey: string; name: string; contentType: string; sizeBytes: number | null }
38
38
 
39
- class AttachmentStorageError extends Schema.TaggedErrorClass<AttachmentStorageError>()('AttachmentStorageError', {
40
- operation: Schema.String,
41
- message: Schema.String,
42
- cause: Schema.Defect,
43
- }) {}
39
+ class AttachmentStorageError extends Schema.TaggedErrorClass<AttachmentStorageError>()(
40
+ ERROR_TAGS.AttachmentStorageError,
41
+ { operation: Schema.String, message: Schema.String, cause: Schema.Defect },
42
+ ) {}
44
43
 
45
44
  function toAttachmentStorageError(operation: string, cause: unknown): AttachmentStorageError {
46
45
  return new AttachmentStorageError({ operation, message: getErrorMessage(cause), cause })
@@ -26,7 +26,7 @@ function buildGeneratedDocumentStorageKey(params: {
26
26
  }
27
27
 
28
28
  class GeneratedDocumentStorageError extends Schema.TaggedErrorClass<GeneratedDocumentStorageError>()(
29
- 'GeneratedDocumentStorageError',
29
+ '@lota-sdk/core/GeneratedDocumentStorageError',
30
30
  { message: Schema.String, cause: Schema.Defect },
31
31
  ) {}
32
32
 
@@ -1,7 +1,7 @@
1
1
  import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
 
4
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -31,12 +31,13 @@ The caller enforces a structured output schema with exactly one field:
31
31
  </output-format>
32
32
  </agent-instructions>`
33
33
 
34
- export function createContextCompactionAgent(options: CreateHelperToolLoopAgentOptions) {
35
- return new ToolLoopAgent({
36
- id: 'context-compaction',
37
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
38
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
39
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
40
- ...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
41
- })
34
+ export function makeContextCompactionAgentFactory(models: AiGatewayModels) {
35
+ return (options: CreateHelperToolLoopAgentOptions) =>
36
+ new ToolLoopAgent({
37
+ id: 'context-compaction',
38
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
39
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
40
+ providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
41
+ ...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
42
+ })
42
43
  }