@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,8 +1,13 @@
1
1
  import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, SystemPlanNodeOwner } from '@lota-sdk/shared'
2
2
  import { Context, Effect, Layer } from 'effect'
3
3
 
4
- import { BadRequestError } from '../effect/errors'
4
+ import { BadRequestError, ServiceError } from '../effect/errors'
5
+ import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
5
6
  import { RuntimeConfigServiceTag } from '../effect/services'
7
+
8
+ const trySystemExecutorPromise = makeEffectTryPromiseWithMessage(
9
+ (message, cause) => new ServiceError({ message, cause }),
10
+ )
6
11
  import type { SystemNodeExecutor, PluginNodeExecutionParams } from '../runtime/plugin-types'
7
12
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
8
13
  import type { PlanValidationIssueInput } from './plan/plan-validator.service'
@@ -86,27 +91,27 @@ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
86
91
  return []
87
92
  },
88
93
 
89
- executeNode(params: {
94
+ executeNode: Effect.fn('SystemExecutor.executeNode')(function* (params: {
90
95
  nodeSpec: PlanNodeSpec
91
96
  resolvedInput: Record<string, unknown>
92
97
  context: OwnershipDispatchContext
93
98
  }) {
94
- return Effect.gen(function* () {
95
- const owner = params.nodeSpec.owner
96
- if (!isSystemOwner(owner)) {
97
- return yield* new BadRequestError({
98
- message: `SystemExecutor cannot execute owner type "${owner.executorType}".`,
99
- })
100
- }
99
+ const owner = params.nodeSpec.owner
100
+ if (!isSystemOwner(owner)) {
101
+ return yield* new BadRequestError({
102
+ message: `SystemExecutor cannot execute owner type "${owner.executorType}".`,
103
+ })
104
+ }
101
105
 
102
- const executor = getSystemExecutors()[owner.ref]
103
- if (!executor || !executor.supportedOperations.includes(owner.operation)) {
104
- return yield* new BadRequestError({
105
- message: `System executor ${owner.ref}.${owner.operation} is not registered.`,
106
- })
107
- }
106
+ const executor = getSystemExecutors()[owner.ref]
107
+ if (!executor || !executor.supportedOperations.includes(owner.operation)) {
108
+ return yield* new BadRequestError({
109
+ message: `System executor ${owner.ref}.${owner.operation} is not registered.`,
110
+ })
111
+ }
108
112
 
109
- return yield* Effect.tryPromise(() =>
113
+ return yield* trySystemExecutorPromise(
114
+ () =>
110
115
  executor.executeNode(
111
116
  buildSystemExecutionParams({
112
117
  owner,
@@ -115,16 +120,16 @@ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
115
120
  context: params.context,
116
121
  }),
117
122
  ),
118
- )
119
- })
120
- },
123
+ `System executor "${owner.ref}.${owner.operation}" failed.`,
124
+ ).pipe(Effect.withSpan('SystemExecutor.invoke'))
125
+ }),
121
126
  }
122
127
  }
123
128
 
124
129
  export class SystemExecutorServiceTag extends Context.Service<
125
130
  SystemExecutorServiceTag,
126
131
  ReturnType<typeof makeSystemExecutorService>
127
- >()('SystemExecutorService') {}
132
+ >()('@lota-sdk/core/SystemExecutorService') {}
128
133
 
129
134
  export const SystemExecutorServiceLive = Layer.effect(
130
135
  SystemExecutorServiceTag,
@@ -8,6 +8,7 @@ import type { RecordIdRef } from '../../db/record-id'
8
8
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
9
9
  import { TABLES } from '../../db/tables'
10
10
  import { BadRequestError, DatabaseError } from '../../effect/errors'
11
+ import type { LockAcquisitionError, LockLostError, NotFoundError, RedisError, ServiceError } from '../../effect/errors'
11
12
  import type { RedisConnectionManager } from '../../redis/connection'
12
13
  import { withLeaseLock } from '../../redis/redis-lease-lock'
13
14
  import type { makeThreadMessageService } from './thread-message.service'
@@ -31,7 +32,22 @@ function haveSameMembers(left: string[], right: string[]): boolean {
31
32
  return left.length === right.length && left.every((value, index) => value === right[index])
32
33
  }
33
34
 
34
- type NormalizedThreadFactory = (thread: ThreadRecord) => Effect.Effect<NormalizedThread, unknown>
35
+ type ThreadNormalizationError = BadRequestError | ServiceError
36
+ type ThreadBootstrapError =
37
+ | BadRequestError
38
+ | DatabaseError
39
+ | LockAcquisitionError
40
+ | LockLostError
41
+ | NotFoundError
42
+ | RedisError
43
+ | ServiceError
44
+
45
+ type NormalizedThreadFactory = (thread: ThreadRecord) => Effect.Effect<NormalizedThread, ThreadNormalizationError>
46
+ type DuplicateCreateErrorLike = { cause?: unknown; message?: unknown }
47
+
48
+ function isDuplicateCreateErrorLike(value: unknown): value is DuplicateCreateErrorLike {
49
+ return (typeof value === 'object' || typeof value === 'function') && value !== null
50
+ }
35
51
 
36
52
  export function createThreadBootstrapHelpers(deps: {
37
53
  threadStore: ThreadRecordStore
@@ -85,26 +101,26 @@ export function createThreadBootstrapHelpers(deps: {
85
101
  const seen = new Set<unknown>()
86
102
  let current = error
87
103
 
88
- while ((typeof current === 'object' || typeof current === 'function') && current !== null && !seen.has(current)) {
104
+ while (isDuplicateCreateErrorLike(current) && !seen.has(current)) {
89
105
  seen.add(current)
90
- const message = Reflect.get(current, 'message')
91
- if (typeof message === 'string' && message.includes('already contains')) {
106
+ if (typeof current.message === 'string' && current.message.includes('already contains')) {
92
107
  return true
93
108
  }
94
- current = Reflect.get(current, 'cause')
109
+ current = current.cause
95
110
  }
96
111
 
97
112
  return false
98
113
  }
99
114
 
100
- const normalizeThreadEffect = (thread: ThreadRecord) => deps.normalizeThread(thread)
115
+ const normalizeThreadEffect = (thread: ThreadRecord): Effect.Effect<NormalizedThread, ThreadNormalizationError> =>
116
+ deps.normalizeThread(thread)
101
117
 
102
118
  const getOrCreateDefaultEffect = (
103
119
  orgId: RecordIdRef,
104
120
  userId: RecordIdRef,
105
121
  agentId: string,
106
122
  config?: { title?: string; nameGenerated?: boolean },
107
- ) => {
123
+ ): Effect.Effect<{ created: boolean; record: ThreadRecord }, DatabaseError | NotFoundError> => {
108
124
  const lookup = { type: 'default' as const, organizationId: orgId, userId, agentId }
109
125
 
110
126
  return Effect.gen(function* () {
@@ -150,7 +166,7 @@ export function createThreadBootstrapHelpers(deps: {
150
166
  userId: RecordIdRef,
151
167
  threadType: string,
152
168
  config: { members: string[]; title: string; nameGenerated?: boolean },
153
- ) => {
169
+ ): Effect.Effect<{ created: boolean; record: ThreadRecord }, DatabaseError | NotFoundError> => {
154
170
  const lookup = { type: 'thread' as const, organizationId: orgId, userId, threadType }
155
171
 
156
172
  return Effect.gen(function* () {
@@ -199,7 +215,7 @@ export function createThreadBootstrapHelpers(deps: {
199
215
  threadType?: string
200
216
  members?: string[]
201
217
  title?: string
202
- }) =>
218
+ }): Effect.Effect<NormalizedThread, ThreadBootstrapError> =>
203
219
  Effect.gen(function* () {
204
220
  switch (input.type) {
205
221
  case 'default':
@@ -262,7 +278,7 @@ export function createThreadBootstrapHelpers(deps: {
262
278
  userId: RecordIdRef,
263
279
  orgId: RecordIdRef,
264
280
  options?: { onboardStatus?: string; userName?: string | null },
265
- ) => {
281
+ ): Effect.Effect<void, ThreadBootstrapError> => {
266
282
  const bootstrapConfig = getThreadBootstrapConfig()
267
283
 
268
284
  return withLeaseLock(
@@ -5,6 +5,7 @@ import { BoundQuery } from 'surrealdb'
5
5
  import type { RecordIdRef } from '../../db/record-id'
6
6
  import type { SurrealDBService } from '../../db/service'
7
7
  import { TABLES } from '../../db/tables'
8
+ import type { BadRequestError, ServiceError } from '../../effect/errors'
8
9
  import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
9
10
  import { ThreadSchema } from './thread.types'
10
11
  import type { NormalizedThread, ThreadRecord } from './thread.types'
@@ -38,7 +39,7 @@ export function createThreadListingHelpers(deps: {
38
39
  normalizeThreads(
39
40
  threads: ThreadRecord[],
40
41
  options?: { checkLease?: boolean },
41
- ): Effect.Effect<NormalizedThread[], unknown>
42
+ ): Effect.Effect<NormalizedThread[], BadRequestError | ServiceError>
42
43
  }) {
43
44
  class ThreadListingError extends Schema.TaggedErrorClass<ThreadListingError>()('ThreadListingError', {
44
45
  message: Schema.String,
@@ -1,3 +1,4 @@
1
+ import type { Context } from 'effect'
1
2
  import { Schema, Effect } from 'effect'
2
3
 
3
4
  import { serverLogger } from '../../config/logger'
@@ -9,16 +10,19 @@ import {
9
10
  appendToMemoryBlock,
10
11
  compactMemoryBlockEntries,
11
12
  formatPersistedMemoryBlockForPrompt,
13
+ MemoryBlockCompactError,
12
14
  parseMemoryBlock,
13
15
  serializeMemoryBlock,
14
16
  } from '../../runtime/memory/memory-block'
17
+ import type { BackgroundWorkService } from '../background-work.service'
18
+ import type { makeContextCompactionService } from '../context-compaction.service'
15
19
  import { MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES, MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES } from './thread-constants'
16
20
  import type { ThreadRecordStore } from './thread-record-store'
17
21
  import type { ThreadRecord } from './thread.types'
18
22
 
19
- type ContextCompactionServiceLike = {
20
- compactMemoryBlock(params: { previousSummary: string; newEntriesText: string }): Effect.Effect<string, unknown>
21
- }
23
+ type ContextCompactionServiceLike = Pick<ReturnType<typeof makeContextCompactionService>, 'compactMemoryBlock'>
24
+
25
+ type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkService>
22
26
 
23
27
  class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()('ThreadMemoryBlockError', {
24
28
  message: Schema.String,
@@ -39,6 +43,7 @@ export function formatMemoryBlockForPrompt(thread: Pick<ThreadRecord, 'memoryBlo
39
43
  export function createThreadMemoryBlockHelpers(deps: {
40
44
  threadStore: ThreadRecordStore
41
45
  contextCompactionService: ContextCompactionServiceLike
46
+ background: BackgroundWorker
42
47
  }) {
43
48
  function appendMemoryBlock(threadId: RecordIdRef, entry: string): Effect.Effect<string, ThreadMemoryBlockError> {
44
49
  return Effect.gen(function* () {
@@ -63,7 +68,7 @@ export function createThreadMemoryBlockHelpers(deps: {
63
68
  )
64
69
 
65
70
  if (updatedEntries.length >= MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES) {
66
- yield* Effect.forkDetach(
71
+ yield* deps.background.run(
67
72
  compactMemoryBlock(threadRef).pipe(
68
73
  Effect.catch((error: unknown) =>
69
74
  Effect.sync(() => {
@@ -71,6 +76,7 @@ export function createThreadMemoryBlockHelpers(deps: {
71
76
  }),
72
77
  ),
73
78
  ),
79
+ 'thread-memory-block.compactMemoryBlock',
74
80
  )
75
81
  }
76
82
 
@@ -93,7 +99,14 @@ export function createThreadMemoryBlockHelpers(deps: {
93
99
  entries: parseMemoryBlock(thread.memoryBlock),
94
100
  triggerEntries: MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES,
95
101
  chunkEntries: MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES,
96
- compact: (params) => deps.contextCompactionService.compactMemoryBlock(params),
102
+ compact: (params) =>
103
+ deps.contextCompactionService
104
+ .compactMemoryBlock(params)
105
+ .pipe(
106
+ Effect.mapError(
107
+ (cause) => new MemoryBlockCompactError({ message: 'compact callback failed', cause }),
108
+ ),
109
+ ),
97
110
  }),
98
111
  `Failed to compact memory block for thread ${threadIdString}`,
99
112
  )
@@ -3,6 +3,7 @@ import type { ChatMessage } from '@lota-sdk/shared'
3
3
  import { Context, Effect, Layer } from 'effect'
4
4
  import { RecordId, surql } from 'surrealdb'
5
5
  import { z } from 'zod'
6
+ import type { ZodTypeAny } from 'zod'
6
7
 
7
8
  import { getAgentDisplayNames } from '../../config/agent-defaults'
8
9
  import { CursorRowSchema, listMessageHistoryPageEffect } from '../../db/cursor-pagination'
@@ -21,6 +22,17 @@ import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
21
22
 
22
23
  const ThreadMessageExistingRowSchema = z.object({ id: recordIdSchema, createdAt: z.coerce.date() })
23
24
 
25
+ function parseRowOrFail<TSchema extends ZodTypeAny>(
26
+ schema: TSchema,
27
+ value: unknown,
28
+ operation: string,
29
+ ): Effect.Effect<z.infer<TSchema>, ServiceError> {
30
+ return Effect.try({
31
+ try: () => schema.parse(value) as z.infer<TSchema>,
32
+ catch: (cause) => new ServiceError({ message: `Failed to parse row for ${operation}.`, cause }),
33
+ })
34
+ }
35
+
24
36
  function toMessageId(value: string | RecordIdRef): string {
25
37
  return recordIdToString(value, TABLES.THREAD_MESSAGE)
26
38
  }
@@ -160,7 +172,10 @@ export function makeThreadMessageService(db: SurrealDBService) {
160
172
  `),
161
173
  'Failed to list thread messages.',
162
174
  ).pipe(
163
- Effect.map((rows) => rows.map((row) => ThreadMessageRowSchema.parse(row)).map((row) => toChatMessage(row))),
175
+ Effect.flatMap((rows) =>
176
+ Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listMessages')),
177
+ ),
178
+ Effect.map((rows) => rows.map((row) => toChatMessage(row))),
164
179
  )
165
180
  },
166
181
  listMessagesEffect(threadId: RecordIdRef) {
@@ -210,7 +225,10 @@ export function makeThreadMessageService(db: SurrealDBService) {
210
225
  `),
211
226
  'Failed to list thread messages after cursor.',
212
227
  )
213
- return rows.map((row) => ThreadMessageRowSchema.parse(row)).map((row) => toChatMessage(row))
228
+ const parsedRows = yield* Effect.forEach(rows, (row) =>
229
+ parseRowOrFail(ThreadMessageRowSchema, row, 'listMessagesAfterCursor'),
230
+ )
231
+ return parsedRows.map((row) => toChatMessage(row))
214
232
  })
215
233
  },
216
234
  listMessagesAfterCursorEffect(threadId: RecordIdRef, afterMessageId?: string) {
@@ -229,12 +247,10 @@ export function makeThreadMessageService(db: SurrealDBService) {
229
247
  `),
230
248
  'Failed to list recent thread messages.',
231
249
  ).pipe(
232
- Effect.map((rows) =>
233
- rows
234
- .map((row) => ThreadMessageRowSchema.parse(row))
235
- .reverse()
236
- .map((row) => toChatMessage(row)),
250
+ Effect.flatMap((rows) =>
251
+ Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listRecentMessages')),
237
252
  ),
253
+ Effect.map((rows) => rows.reverse().map((row) => toChatMessage(row))),
238
254
  )
239
255
  },
240
256
  listRecentMessagesEffect(threadId: RecordIdRef, limit: number) {
@@ -352,7 +368,7 @@ export function makeThreadMessageService(db: SurrealDBService) {
352
368
  export class ThreadMessageServiceTag extends Context.Service<
353
369
  ThreadMessageServiceTag,
354
370
  ReturnType<typeof makeThreadMessageService>
355
- >()('ThreadMessageService') {}
371
+ >()('@lota-sdk/core/ThreadMessageService') {}
356
372
 
357
373
  export const ThreadMessageServiceLive = Layer.effect(
358
374
  ThreadMessageServiceTag,
@@ -62,7 +62,7 @@ export function makeThreadTitleService(
62
62
  export class ThreadTitleServiceTag extends Context.Service<
63
63
  ThreadTitleServiceTag,
64
64
  ReturnType<typeof makeThreadTitleService>
65
- >()('ThreadTitleService') {}
65
+ >()('@lota-sdk/core/ThreadTitleService') {}
66
66
 
67
67
  export const ThreadTitleServiceLive = Layer.effect(
68
68
  ThreadTitleServiceTag,
@@ -142,7 +142,7 @@ export function createThreadTurnVisibleAgentRunner<TBuildTurnToolParams>(
142
142
  aiLogger.error`Agent run failed (agent=${agentId}): ${error}`
143
143
  },
144
144
  recordAbort: (error: unknown) => {
145
- aiLogger.info`Agent run aborted (agent=${agentId}): ${error instanceof Error ? error.message : String(error)}`
145
+ aiLogger.debug`Agent run aborted (agent=${agentId}): ${error instanceof Error ? error.message : String(error)}`
146
146
  },
147
147
  })
148
148
 
@@ -570,20 +570,22 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
570
570
  return Effect.succeed(preSeededMemoriesByAgent.get(agentId))
571
571
  }
572
572
 
573
- return Effect.gen(function* () {
574
- const preSeededMemories = yield* memoryService.getTopMemories({
575
- orgId: orgIdString,
576
- agentName: agentId,
577
- limit: PRESEEDED_MEMORY_LOOKUP_LIMIT,
578
- })
579
- preSeededMemoriesByAgent.set(agentId, preSeededMemories)
580
- return preSeededMemories
581
- }).pipe(
582
- Effect.mapError(
583
- (error) =>
584
- new ThreadTurnStreamingError({ message: `Failed to load pre-seeded memories for ${agentId}.`, cause: error }),
585
- ),
586
- )
573
+ return memoryService
574
+ .getTopMemories({ orgId: orgIdString, agentName: agentId, limit: PRESEEDED_MEMORY_LOOKUP_LIMIT })
575
+ .pipe(
576
+ Effect.tap((preSeededMemories) =>
577
+ Effect.sync(() => {
578
+ preSeededMemoriesByAgent.set(agentId, preSeededMemories)
579
+ }),
580
+ ),
581
+ Effect.mapError(
582
+ (error) =>
583
+ new ThreadTurnStreamingError({
584
+ message: `Failed to load pre-seeded memories for ${agentId}.`,
585
+ cause: error,
586
+ }),
587
+ ),
588
+ )
587
589
  }
588
590
 
589
591
  const learnedSkillsByAgent = new Map<string, string | undefined>()
@@ -1083,7 +1085,7 @@ export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps
1083
1085
  randomId: () => Bun.randomUUIDv7(),
1084
1086
  }),
1085
1087
  }
1086
- const annotateTurnSpans = <A, E>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E>) =>
1088
+ const annotateTurnSpans = <A, E, R>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E, R>) =>
1087
1089
  effect.pipe(
1088
1090
  Effect.annotateSpans(
1089
1091
  compactSpanAttributes({
@@ -1116,7 +1118,7 @@ export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps
1116
1118
  export class ThreadTurnPreparationServiceTag extends Context.Service<
1117
1119
  ThreadTurnPreparationServiceTag,
1118
1120
  ReturnType<typeof makeThreadTurnPreparationService>
1119
- >()('ThreadTurnPreparationService') {}
1121
+ >()('@lota-sdk/core/ThreadTurnPreparationService') {}
1120
1122
 
1121
1123
  export const ThreadTurnPreparationServiceLive = Layer.effect(
1122
1124
  ThreadTurnPreparationServiceTag,
@@ -7,7 +7,7 @@ import { Effect, Ref, Schema, Stream } from 'effect'
7
7
  import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../../config/agent-defaults'
8
8
  import { aiLogger } from '../../config/logger'
9
9
  import type { RecordIdRef } from '../../db/record-id'
10
- import { effectTryMaybeAsync, effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
10
+ import { effectTryMaybeAsync, makeEffectTryPromiseWithMessage } from '../../effect/helpers'
11
11
  import { runPromise } from '../../effect/runtime'
12
12
  import {
13
13
  readRuntimeAgentIdentityOverrides,
@@ -44,12 +44,9 @@ export class ThreadTurnStreamingError extends Schema.TaggedErrorClass<ThreadTurn
44
44
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
45
45
  ) {}
46
46
 
47
- function effectTryPromise<A>(
48
- evaluate: () => PromiseLike<A>,
49
- message: string,
50
- ): Effect.Effect<A, ThreadTurnStreamingError> {
51
- return effectTryPromiseShared(evaluate, (error) => new ThreadTurnStreamingError({ message, cause: error }))
52
- }
47
+ const effectTryPromise = makeEffectTryPromiseWithMessage(
48
+ (message, cause) => new ThreadTurnStreamingError({ message, cause }),
49
+ )
53
50
 
54
51
  function effectFromMaybeEffect<A>(
55
52
  evaluate: () => A | PromiseLike<A> | Effect.Effect<A, ThreadTurnStreamingError>,
@@ -93,7 +90,9 @@ function isTextTokenChunkType(chunkType: string | undefined): boolean {
93
90
  return chunkType === 'text-delta'
94
91
  }
95
92
 
96
- function buildFallbackResponseMessage(result: ToolLoopGenerateResult): ChatMessage {
93
+ function buildFallbackResponseMessage(
94
+ result: ToolLoopGenerateResult,
95
+ ): Effect.Effect<ChatMessage, ThreadTurnStreamingError> {
97
96
  const parts: ChatMessage['parts'] = []
98
97
 
99
98
  for (const step of result.steps ?? []) {
@@ -114,10 +113,12 @@ function buildFallbackResponseMessage(result: ToolLoopGenerateResult): ChatMessa
114
113
  }
115
114
 
116
115
  if (parts.length === 0) {
117
- throw new Error('Agent generate fallback did not produce any response parts.')
116
+ return Effect.fail(
117
+ new ThreadTurnStreamingError({ message: 'Agent generate fallback did not produce any response parts.' }),
118
+ )
118
119
  }
119
120
 
120
- return { id: Bun.randomUUIDv7(), role: 'assistant', parts }
121
+ return Effect.succeed({ id: Bun.randomUUIDv7(), role: 'assistant', parts })
121
122
  }
122
123
 
123
124
  export interface StreamAgentResponseContext {
@@ -287,7 +288,7 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
287
288
  aiLogger.warn`Agent stream failed for ${resolvedAgentId}; falling back to generate: ${cause.message}`
288
289
  }),
289
290
  ),
290
- Effect.map((result) => buildFallbackResponseMessage(result as ToolLoopGenerateResult)),
291
+ Effect.flatMap((result) => buildFallbackResponseMessage(result as ToolLoopGenerateResult)),
291
292
  )
292
293
 
293
294
  const result = yield* effectTryPromise(
@@ -6,7 +6,6 @@ import { ensureRecordId, recordIdToString } from '../../db/record-id'
6
6
  import { TABLES } from '../../db/tables'
7
7
  import { BadRequestError } from '../../effect/errors'
8
8
  import { runPromise } from '../../effect/runtime'
9
- import { getCurrentRuntime } from '../../effect/runtime-ref'
10
9
  import { hasApprovalRespondedParts, isApprovalContinuationRequest } from '../../runtime/approval-continuation'
11
10
  import { shouldPlanNodeUseVisibleTurn } from '../../runtime/execution-plan-visibility'
12
11
  import { wrapResponseWithKeepalive } from '../../utils/sse-keepalive'
@@ -299,7 +298,7 @@ export function makeThreadTurnService(deps: ThreadTurnDeps) {
299
298
  export class ThreadTurnServiceTag extends Context.Service<
300
299
  ThreadTurnServiceTag,
301
300
  ReturnType<typeof makeThreadTurnService>
302
- >()('ThreadTurnService') {}
301
+ >()('@lota-sdk/core/ThreadTurnService') {}
303
302
 
304
303
  export const ThreadTurnServiceLive = Layer.effect(
305
304
  ThreadTurnServiceTag,
@@ -313,24 +312,58 @@ export const ThreadTurnServiceLive = Layer.effect(
313
312
  }),
314
313
  )
315
314
 
316
- function getThreadTurnService() {
317
- return getCurrentRuntime().runSync(Effect.service(ThreadTurnServiceTag))
318
- }
315
+ const createThreadApprovalContinuationStreamWithRuntime = Effect.fn(
316
+ 'ThreadTurn.createApprovalContinuationStreamWithRuntime',
317
+ )(function* (params: ThreadApprovalContinuationParams) {
318
+ const threadTurnService = yield* ThreadTurnServiceTag
319
+ return yield* threadTurnService.createThreadApprovalContinuationStream(params)
320
+ })
321
+
322
+ const createThreadNativeToolApprovalStreamWithRuntime = Effect.fn(
323
+ 'ThreadTurn.createNativeToolApprovalStreamWithRuntime',
324
+ )(function* (params: ThreadApprovalContinuationParams) {
325
+ const threadTurnService = yield* ThreadTurnServiceTag
326
+ return yield* threadTurnService.createThreadNativeToolApprovalStream(params)
327
+ })
328
+
329
+ const createThreadTurnStreamWithRuntime = Effect.fn('ThreadTurn.createThreadTurnStreamWithRuntime')(function* (
330
+ params: ThreadTurnParams,
331
+ ) {
332
+ const threadTurnService = yield* ThreadTurnServiceTag
333
+ return yield* threadTurnService.createThreadTurnStream(params)
334
+ })
335
+
336
+ const runThreadTurnInBackgroundWithRuntime = Effect.fn('ThreadTurn.runThreadTurnInBackgroundWithRuntime')(function* (
337
+ params: ThreadTurnParams,
338
+ ) {
339
+ const threadTurnService = yield* ThreadTurnServiceTag
340
+ return yield* threadTurnService.runThreadTurnInBackground(params)
341
+ })
342
+
343
+ const triggerPlanNodeTurnWithRuntime = Effect.fn('ThreadTurn.triggerPlanNodeTurnWithRuntime')(function* (params: {
344
+ runId: string
345
+ nodeId: string
346
+ abortSignal?: AbortSignal
347
+ streamId?: string
348
+ }) {
349
+ const threadTurnService = yield* ThreadTurnServiceTag
350
+ return yield* threadTurnService.triggerPlanNodeTurn(params)
351
+ })
319
352
 
320
353
  export function createThreadApprovalContinuationStream(params: ThreadApprovalContinuationParams) {
321
- return runPromise(getThreadTurnService().createThreadApprovalContinuationStream(params))
354
+ return runPromise(createThreadApprovalContinuationStreamWithRuntime(params))
322
355
  }
323
356
 
324
357
  export function createThreadNativeToolApprovalStream(params: ThreadApprovalContinuationParams) {
325
- return runPromise(getThreadTurnService().createThreadNativeToolApprovalStream(params))
358
+ return runPromise(createThreadNativeToolApprovalStreamWithRuntime(params))
326
359
  }
327
360
 
328
361
  export function createThreadTurnStream(params: ThreadTurnParams) {
329
- return runPromise(getThreadTurnService().createThreadTurnStream(params))
362
+ return runPromise(createThreadTurnStreamWithRuntime(params))
330
363
  }
331
364
 
332
365
  export function runThreadTurnInBackground(params: ThreadTurnParams): Promise<PreparedThreadTurnResult> {
333
- return runPromise(getThreadTurnService().runThreadTurnInBackground(params))
366
+ return runPromise(runThreadTurnInBackgroundWithRuntime(params))
334
367
  }
335
368
 
336
369
  export function triggerPlanNodeTurn(params: {
@@ -339,5 +372,5 @@ export function triggerPlanNodeTurn(params: {
339
372
  abortSignal?: AbortSignal
340
373
  streamId?: string
341
374
  }): Promise<PreparedThreadTurnResult> {
342
- return runPromise(getThreadTurnService().triggerPlanNodeTurn(params))
375
+ return runPromise(triggerPlanNodeTurnWithRuntime(params))
343
376
  }
@@ -13,6 +13,7 @@ import { DatabaseServiceTag, RedisServiceTag } from '../../effect/services'
13
13
  import type { RedisConnectionManager } from '../../redis/connection'
14
14
  import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
15
15
  import { toIsoDateTimeString } from '../../utils/date-time'
16
+ import { BackgroundWorkService } from '../background-work.service'
16
17
  import { ChatRunRegistryTag } from '../chat-run-registry.service'
17
18
  import { ContextCompactionServiceTag } from '../context-compaction.service'
18
19
  import type { makeContextCompactionService } from '../context-compaction.service'
@@ -68,6 +69,7 @@ interface ThreadServiceDeps {
68
69
  threadMessageService: ReturnType<typeof makeThreadMessageService>
69
70
  contextCompactionService: ReturnType<typeof makeContextCompactionService>
70
71
  compactionCoordination: CompactionCoordination
72
+ background: Context.Service.Shape<typeof BackgroundWorkService>
71
73
  }
72
74
 
73
75
  export function makeThreadService(deps: ThreadServiceDeps) {
@@ -115,7 +117,7 @@ export function makeThreadService(deps: ThreadServiceDeps) {
115
117
  normalizeRecordIdStringEffect(thread.userId, TABLES.USER),
116
118
  normalizeRecordIdStringEffect(thread.organizationId, TABLES.ORGANIZATION),
117
119
  ])
118
- return NormalizedThreadSchema.parse({
120
+ const candidate = {
119
121
  id,
120
122
  userId,
121
123
  organizationId,
@@ -131,6 +133,10 @@ export function makeThreadService(deps: ThreadServiceDeps) {
131
133
  members: thread.members,
132
134
  createdAt: toIsoDateTimeString(thread.createdAt),
133
135
  updatedAt: toIsoDateTimeString(thread.updatedAt),
136
+ }
137
+ return yield* Effect.try({
138
+ try: () => NormalizedThreadSchema.parse(candidate),
139
+ catch: (cause) => new ServiceError({ message: 'Failed to parse normalized thread.', cause }),
134
140
  })
135
141
  })
136
142
  }
@@ -154,6 +160,7 @@ export function makeThreadService(deps: ThreadServiceDeps) {
154
160
  const memory = createThreadMemoryBlockHelpers({
155
161
  threadStore,
156
162
  contextCompactionService: { compactMemoryBlock: deps.contextCompactionService.compactMemoryBlock },
163
+ background: deps.background,
157
164
  })
158
165
 
159
166
  function getById(threadId: RecordIdRef) {
@@ -311,7 +318,7 @@ export function makeThreadService(deps: ThreadServiceDeps) {
311
318
  }
312
319
 
313
320
  export class ThreadServiceTag extends Context.Service<ThreadServiceTag, ReturnType<typeof makeThreadService>>()(
314
- 'ThreadService',
321
+ '@lota-sdk/core/ThreadService',
315
322
  ) {}
316
323
 
317
324
  export const ThreadServiceLive = Layer.effect(
@@ -323,6 +330,7 @@ export const ThreadServiceLive = Layer.effect(
323
330
  const threadMessageService = yield* ThreadMessageServiceTag
324
331
  const contextCompactionService = yield* ContextCompactionServiceTag
325
332
  const compactionCoordination = yield* CompactionCoordinationTag
333
+ const background = yield* BackgroundWorkService
326
334
  return makeThreadService({
327
335
  db,
328
336
  redis,
@@ -330,6 +338,7 @@ export const ThreadServiceLive = Layer.effect(
330
338
  threadMessageService,
331
339
  contextCompactionService,
332
340
  compactionCoordination,
341
+ background,
333
342
  })
334
343
  }),
335
344
  )
@@ -94,7 +94,7 @@ export function makeUserService(db: SurrealDBService) {
94
94
  }
95
95
 
96
96
  export class UserServiceTag extends Context.Service<UserServiceTag, ReturnType<typeof makeUserService>>()(
97
- 'UserService',
97
+ '@lota-sdk/core/UserService',
98
98
  ) {}
99
99
 
100
100
  export const UserServiceLive = Layer.effect(
@@ -85,7 +85,7 @@ export function makeWriteIntentValidatorService() {
85
85
  export class WriteIntentValidatorServiceTag extends Context.Service<
86
86
  WriteIntentValidatorServiceTag,
87
87
  ReturnType<typeof makeWriteIntentValidatorService>
88
- >()('WriteIntentValidatorService') {}
88
+ >()('@lota-sdk/core/WriteIntentValidatorService') {}
89
89
 
90
90
  export const WriteIntentValidatorServiceLive = Layer.succeed(
91
91
  WriteIntentValidatorServiceTag,