@lota-sdk/core 0.4.13 → 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 (138) 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/memory/memory-block.ts +3 -9
  37. package/src/runtime/memory/memory-scope.ts +3 -1
  38. package/src/runtime/plugin-resolution.ts +2 -1
  39. package/src/runtime/post-turn-side-effects.ts +6 -5
  40. package/src/runtime/retrieval-adapters.ts +8 -20
  41. package/src/runtime/runtime-config.ts +3 -9
  42. package/src/runtime/runtime-extensions.ts +2 -4
  43. package/src/runtime/runtime-lifecycle.ts +56 -16
  44. package/src/runtime/runtime-services.ts +180 -102
  45. package/src/runtime/runtime-worker-registry.ts +3 -1
  46. package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
  47. package/src/runtime/social-chat/social-chat-history.ts +21 -18
  48. package/src/runtime/social-chat/social-chat.ts +356 -223
  49. package/src/runtime/specialist-runner.ts +3 -1
  50. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
  51. package/src/runtime/thread-turn-context.ts +142 -102
  52. package/src/runtime/turn-lifecycle.ts +15 -46
  53. package/src/services/agent-activity.service.ts +1 -1
  54. package/src/services/agent-executor.service.ts +107 -77
  55. package/src/services/autonomous-job.service.ts +354 -293
  56. package/src/services/background-work.service.ts +3 -3
  57. package/src/services/context-compaction.service.ts +7 -2
  58. package/src/services/document-chunk.service.ts +50 -32
  59. package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
  60. package/src/services/execution-plan/execution-plan.service.ts +162 -179
  61. package/src/services/feedback-loop.service.ts +5 -4
  62. package/src/services/graph-full-routing.ts +37 -36
  63. package/src/services/institutional-memory.service.ts +28 -30
  64. package/src/services/learned-skill.service.ts +107 -72
  65. package/src/services/memory/memory-errors.ts +4 -23
  66. package/src/services/memory/memory-org-memory.ts +10 -5
  67. package/src/services/memory/memory-rerank.ts +18 -6
  68. package/src/services/memory/memory.service.ts +170 -111
  69. package/src/services/memory/rerank.service.ts +29 -20
  70. package/src/services/organization-member.service.ts +1 -1
  71. package/src/services/organization.service.ts +69 -75
  72. package/src/services/ownership-dispatcher.service.ts +40 -39
  73. package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
  74. package/src/services/plan/plan-agent-query.service.ts +39 -31
  75. package/src/services/plan/plan-completion-side-effects.ts +13 -17
  76. package/src/services/plan/plan-coordination.service.ts +2 -1
  77. package/src/services/plan/plan-cycle.service.ts +6 -5
  78. package/src/services/plan/plan-deadline.service.ts +57 -54
  79. package/src/services/plan/plan-event-delivery.service.ts +5 -4
  80. package/src/services/plan/plan-executor-graph.ts +18 -15
  81. package/src/services/plan/plan-executor.service.ts +235 -262
  82. package/src/services/plan/plan-run.service.ts +169 -93
  83. package/src/services/plan/plan-scheduler.service.ts +192 -202
  84. package/src/services/plan/plan-template.service.ts +1 -1
  85. package/src/services/plan/plan-transaction-events.ts +1 -1
  86. package/src/services/plan/plan-workspace.service.ts +23 -14
  87. package/src/services/plugin-executor.service.ts +5 -9
  88. package/src/services/queue-job.service.ts +117 -59
  89. package/src/services/recent-activity-title.service.ts +13 -12
  90. package/src/services/recent-activity.service.ts +6 -1
  91. package/src/services/social-chat-history.service.ts +29 -25
  92. package/src/services/system-executor.service.ts +5 -9
  93. package/src/services/thread/thread-active-run.ts +2 -2
  94. package/src/services/thread/thread-listing.ts +61 -57
  95. package/src/services/thread/thread-memory-block.ts +73 -48
  96. package/src/services/thread/thread-message.service.ts +76 -65
  97. package/src/services/thread/thread-record-store.ts +8 -8
  98. package/src/services/thread/thread-title.service.ts +10 -4
  99. package/src/services/thread/thread-turn-execution.ts +43 -45
  100. package/src/services/thread/thread-turn-preparation.service.ts +257 -135
  101. package/src/services/thread/thread-turn-streaming.ts +82 -85
  102. package/src/services/thread/thread-turn.ts +8 -8
  103. package/src/services/thread/thread.service.ts +135 -100
  104. package/src/services/user.service.ts +45 -48
  105. package/src/storage/attachment-parser.ts +6 -2
  106. package/src/storage/attachment-storage.service.ts +5 -6
  107. package/src/storage/generated-document-storage.service.ts +1 -1
  108. package/src/system-agents/context-compaction.agent.ts +10 -9
  109. package/src/system-agents/delegated-agent-factory.ts +30 -6
  110. package/src/system-agents/memory-reranker.agent.ts +10 -9
  111. package/src/system-agents/memory.agent.ts +10 -9
  112. package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
  113. package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
  114. package/src/system-agents/skill-extractor.agent.ts +13 -12
  115. package/src/system-agents/skill-manager.agent.ts +13 -12
  116. package/src/system-agents/thread-router.agent.ts +10 -5
  117. package/src/system-agents/title-generator.agent.ts +13 -12
  118. package/src/tools/fetch-webpage.tool.ts +13 -13
  119. package/src/tools/memory-block.tool.ts +3 -1
  120. package/src/tools/plan-approval.tool.ts +4 -2
  121. package/src/tools/read-file-parts.tool.ts +10 -4
  122. package/src/tools/remember-memory.tool.ts +3 -1
  123. package/src/tools/research-topic.tool.ts +9 -5
  124. package/src/tools/search-web.tool.ts +16 -16
  125. package/src/tools/search.tool.ts +20 -5
  126. package/src/tools/team-think.tool.ts +61 -38
  127. package/src/utils/async.ts +5 -5
  128. package/src/utils/errors.ts +19 -18
  129. package/src/utils/sse-keepalive.ts +28 -25
  130. package/src/workers/bootstrap.ts +75 -11
  131. package/src/workers/memory-consolidation.worker.ts +82 -91
  132. package/src/workers/organization-learning.worker.ts +14 -4
  133. package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
  134. package/src/workers/skill-extraction.runner.ts +97 -61
  135. package/src/workers/utils/repo-structure-extractor.ts +13 -8
  136. package/src/workers/utils/thread-message-query.ts +24 -24
  137. package/src/workers/worker-utils.ts +23 -4
  138. package/src/effect/helpers.ts +0 -123
@@ -12,14 +12,15 @@ import { tool as createTool, validateUIMessages } from 'ai'
12
12
  import type { UIMessageStreamWriter } from 'ai'
13
13
  import { Clock, Context, Schema, Effect, Layer } from 'effect'
14
14
 
15
- import type { CoreThreadProfile } from '../../config/agent-defaults'
15
+ import { AiGatewayModelsTag, RuntimeBridgeTag } from '../../ai-gateway/ai-gateway'
16
+ import type { AiGatewayModels } from '../../ai-gateway/ai-gateway'
17
+ import type { CoreThreadProfile, ResolvedAgentConfig } from '../../config/agent-defaults'
16
18
  import { aiLogger } from '../../config/logger'
17
19
  import type { RecordIdRef } from '../../db/record-id'
18
20
  import { recordIdToString } from '../../db/record-id'
19
21
  import { TABLES } from '../../db/tables'
20
22
  import type { DatabaseError, ServiceError } from '../../effect/errors'
21
- import { ThreadTurnError } from '../../effect/errors'
22
- import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
23
+ import { ERROR_TAGS, ThreadTurnError } from '../../effect/errors'
23
24
  import {
24
25
  AgentConfigServiceTag,
25
26
  AgentFactoryServiceTag,
@@ -35,7 +36,7 @@ import {
35
36
  } from '../../runtime/agent-identity-overrides'
36
37
  import { createServerRunAbortController } from '../../runtime/agent-stream-helpers'
37
38
  import { hasApprovalRespondedParts } from '../../runtime/approval-continuation'
38
- import { hasMessageContent } from '../../runtime/chat-message'
39
+ import { hasMessageContent, sanitizePersistedMessages } from '../../runtime/chat-message'
39
40
  import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
40
41
  import { CONTEXT_WINDOW_TOKENS } from '../../runtime/context-compaction/context-compaction-constants'
41
42
  import { createWiredContextCompactionRuntime } from '../../runtime/context-compaction/context-compaction-runtime'
@@ -58,6 +59,7 @@ import { nowEpochMillis } from '../../utils/date-time'
58
59
  import { getErrorMessage } from '../../utils/errors'
59
60
  import type { makeAttachmentService } from '../attachment.service'
60
61
  import { AttachmentServiceTag } from '../attachment.service'
62
+ import { BackgroundWorkServiceTag } from '../background-work.service'
61
63
  import { ChatRunRegistryTag } from '../chat-run-registry.service'
62
64
  import type { makeExecutionPlanService } from '../execution-plan/execution-plan.service'
63
65
  import { ExecutionPlanServiceTag } from '../execution-plan/execution-plan.service'
@@ -67,6 +69,7 @@ import type { createMemoryService } from '../memory/memory.service'
67
69
  import { MemoryServiceTag } from '../memory/memory.service'
68
70
  import type { makePlanRunService } from '../plan/plan-run.service'
69
71
  import { PlanRunServiceTag } from '../plan/plan-run.service'
72
+ import { RecentActivityServiceTag } from '../recent-activity.service'
70
73
  import type { makeThreadMessageService } from './thread-message.service'
71
74
  import { ThreadMessageServiceTag } from './thread-message.service'
72
75
  import {
@@ -84,20 +87,65 @@ import type { NormalizedThread } from './thread.types'
84
87
 
85
88
  interface ThreadTurnPreparationServiceContext {
86
89
  attachmentService: ReturnType<typeof makeAttachmentService>
90
+ agentConfig: ResolvedAgentConfig
91
+ agentFactoryConfig: Context.Service.Shape<typeof AgentFactoryServiceTag>
92
+ runtimeAdapters: Context.Service.Shape<typeof RuntimeAdaptersServiceTag>
93
+ turnHooks: Context.Service.Shape<typeof TurnHooksServiceTag>
94
+ backgroundWork: Context.Service.Shape<typeof BackgroundWorkServiceTag>
87
95
  chatRunRegistry: Context.Service.Shape<typeof ChatRunRegistryTag>
88
96
  compactionCoordination: Context.Service.Shape<typeof CompactionCoordinationTag>
89
97
  executionPlanService: ReturnType<typeof makeExecutionPlanService>
90
98
  learnedSkillService: ReturnType<typeof makeLearnedSkillService>
91
99
  memoryService: ReturnType<typeof createMemoryService>
92
100
  planRunService: ReturnType<typeof makePlanRunService>
101
+ recentActivityService: Context.Service.Shape<typeof RecentActivityServiceTag>
93
102
  threadMessageService: ReturnType<typeof makeThreadMessageService>
94
103
  threadService: ReturnType<typeof makeThreadService>
95
104
  contextCompactionRuntime: ReturnType<typeof createWiredContextCompactionRuntime>
105
+ queues: Context.Service.Shape<typeof LotaQueuesServiceTag>
96
106
  contextCompactionQueue: ContextCompactionQueueRuntime
97
107
  titleGenerationQueue: TitleGenerationQueueRuntime
108
+ aiGatewayModels: AiGatewayModels
109
+ runPromise: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>
98
110
  }
99
111
 
100
112
  type ThreadTurnPreparationRuntimeDeps = ThreadTurnPreparationServiceContext
113
+ type ThreadTurnPreparationSurfaceError = DatabaseError | ThreadTurnError | ThreadTurnPreparationError
114
+ type ThreadTurnPreparationTaggedError =
115
+ | ThreadTurnPreparationSurfaceError
116
+ | ServiceError
117
+ | { readonly _tag: string; readonly message?: string; readonly cause?: unknown }
118
+
119
+ type ThreadTurnRunEffect = Effect.Effect<PreparedThreadTurnResult, ThreadTurnPreparationSurfaceError, never>
120
+
121
+ type ExecuteThreadTurnRunEffect = Effect.Effect<
122
+ PreparedThreadTurnResult | void,
123
+ ThreadTurnPreparationSurfaceError,
124
+ never
125
+ >
126
+
127
+ function isThreadTurnPreparationSurfaceError(
128
+ error: ThreadTurnPreparationTaggedError,
129
+ ): error is ThreadTurnPreparationSurfaceError {
130
+ return (
131
+ error._tag === ERROR_TAGS.DatabaseError ||
132
+ error._tag === ERROR_TAGS.ThreadTurnError ||
133
+ error._tag === ERROR_TAGS.ThreadTurnPreparationError
134
+ )
135
+ }
136
+
137
+ function normalizeThreadTurnPreparationError(
138
+ error: ThreadTurnPreparationTaggedError,
139
+ ): ThreadTurnPreparationSurfaceError {
140
+ if (isThreadTurnPreparationSurfaceError(error)) {
141
+ return error
142
+ }
143
+
144
+ return new ThreadTurnPreparationError({
145
+ message: getErrorMessage(error),
146
+ cause: 'cause' in error ? error.cause : error,
147
+ })
148
+ }
101
149
 
102
150
  const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
103
151
 
@@ -111,13 +159,13 @@ function waitForThreadCompactionIfNeeded(deps: ThreadTurnPreparationRuntimeDeps,
111
159
  }
112
160
 
113
161
  export class ThreadTurnPreparationError extends Schema.TaggedErrorClass<ThreadTurnPreparationError>()(
114
- 'ThreadTurnPreparationError',
162
+ '@lota-sdk/core/ThreadTurnPreparationError',
115
163
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
116
164
  ) {}
117
165
 
118
- const effectTryPromise = makeEffectTryPromiseWithMessage(
119
- (message, cause) => new ThreadTurnPreparationError({ message, cause }),
120
- )
166
+ function toThreadTurnPreparationError(message: string, cause: unknown): ThreadTurnPreparationError {
167
+ return new ThreadTurnPreparationError({ message, cause })
168
+ }
121
169
 
122
170
  export interface ThreadTurnParams {
123
171
  thread: NormalizedThread
@@ -201,16 +249,21 @@ function validateAndHydrateThreadMessagesEffect(params: {
201
249
  errorMessage: string
202
250
  }): Effect.Effect<ChatMessage[], ThreadTurnPreparationError> {
203
251
  const { attachmentService, orgRef, userRef, messages, errorMessage } = params
252
+ const sanitizedMessages = sanitizePersistedMessages(messages)
204
253
 
205
- return effectTryPromise(
206
- () =>
254
+ if (sanitizedMessages.length === 0) {
255
+ return Effect.succeed([])
256
+ }
257
+
258
+ return Effect.tryPromise({
259
+ try: () =>
207
260
  validateUIMessages<ChatMessage>({
208
- messages,
261
+ messages: sanitizedMessages,
209
262
  metadataSchema: messageMetadataSchema,
210
263
  dataSchemas: dataPartsSchemas,
211
264
  }),
212
- errorMessage,
213
- ).pipe(
265
+ catch: (cause) => toThreadTurnPreparationError(errorMessage, cause),
266
+ }).pipe(
214
267
  Effect.map((validated) =>
215
268
  validated.map((message) => hydrateThreadMessageFileUrls(attachmentService, orgRef, userRef, message)),
216
269
  ),
@@ -367,10 +420,10 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
367
420
  deps: ThreadTurnPreparationRuntimeDeps,
368
421
  params: ThreadRunCoreParams,
369
422
  ) {
370
- const runtimeAdapters = yield* RuntimeAdaptersServiceTag
371
- const turnHooks = yield* TurnHooksServiceTag
372
- const agentConfig = yield* AgentConfigServiceTag
373
- const agentFactoryConfig = yield* AgentFactoryServiceTag
423
+ const runtimeAdapters = deps.runtimeAdapters
424
+ const turnHooks = deps.turnHooks
425
+ const agentConfig = deps.agentConfig
426
+ const agentFactoryConfig = deps.agentFactoryConfig
374
427
  const workspaceProvider = runtimeAdapters.workspaceProvider
375
428
  const workspacePromise =
376
429
  params.kind !== 'approvalContinuation' && workspaceProvider
@@ -381,6 +434,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
381
434
  const learnedSkillService = deps.learnedSkillService
382
435
  const memoryService = deps.memoryService
383
436
  const planRunService = deps.planRunService
437
+ const recentActivityService = deps.recentActivityService
384
438
  const threadMessageService = deps.threadMessageService
385
439
  const threadService = deps.threadService
386
440
  const contextCompactionRuntime = deps.contextCompactionRuntime
@@ -509,25 +563,24 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
509
563
  params.agentIdOverride ??
510
564
  (thread.type === 'default' ? thread.agentId : (coreThreadProfile?.config.agentId ?? defaultLeadAgentId))
511
565
  const coreInstructionSections = coreThreadProfile ? [coreThreadProfile.instructions] : undefined
512
- const assembledContext = yield* effectTryPromise(
513
- () =>
514
- assembleThreadTurnContext({
515
- thread,
516
- threadRef,
517
- orgRef,
518
- userRef,
519
- userName,
520
- orgIdString,
521
- userIdString,
522
- messageText,
523
- workspacePromise,
524
- workspaceProvider,
525
- turnHooks,
526
- runtimeAdapters,
527
- pluginRuntime: agentFactoryConfig.pluginRuntime,
528
- }),
529
- 'Failed to assemble thread turn context.',
530
- ).pipe(Effect.withSpan('ThreadTurnPreparation.assembleThreadTurnContext'))
566
+ const assembledContext = yield* assembleThreadTurnContext({
567
+ thread,
568
+ threadRef,
569
+ orgRef,
570
+ userRef,
571
+ userName,
572
+ orgIdString,
573
+ userIdString,
574
+ messageText,
575
+ workspacePromise,
576
+ workspaceProvider,
577
+ turnHooks,
578
+ runtimeAdapters,
579
+ pluginRuntime: agentFactoryConfig.pluginRuntime,
580
+ }).pipe(
581
+ Effect.mapError((cause) => toThreadTurnPreparationError('Failed to assemble thread turn context.', cause)),
582
+ Effect.withSpan('ThreadTurnPreparation.assembleThreadTurnContext'),
583
+ )
531
584
  const {
532
585
  workspace,
533
586
  onboardingActive,
@@ -548,6 +601,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
548
601
  const runs = yield* planRunService.getActiveRunRecords(threadRef)
549
602
  return yield* Effect.forEach(runs, (run) => planRunService.toSerializablePlan(run, { slim: true }))
550
603
  }).pipe(Effect.mapError(toExecutionPlanCacheError)),
604
+ runPromise: deps.runPromise,
551
605
  })
552
606
  const getExecutionPlanInstructionSections = (): Effect.Effect<string[] | undefined, ThreadTurnStreamingError> =>
553
607
  executionPlanInstructionSectionCache
@@ -682,11 +736,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
682
736
  serverRunId: string
683
737
  runAbort: ReturnType<typeof createServerRunAbortController>
684
738
  writer?: UIMessageStreamWriter<ChatMessage>
685
- }): Effect.Effect<
686
- PreparedThreadTurnResult | void,
687
- DatabaseError | ThreadTurnError | ThreadTurnPreparationError,
688
- unknown
689
- > =>
739
+ }): ExecuteThreadTurnRunEffect =>
690
740
  Effect.ensuring(
691
741
  Effect.gen(function* () {
692
742
  const currentRunAbort = runParams.runAbort
@@ -761,20 +811,21 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
761
811
  }),
762
812
  })
763
813
 
764
- yield* effectTryPromise(
765
- () =>
766
- runVisibleAgent({
767
- agentId: planTurn.nodeSpec.owner.ref,
768
- mode: thread.type === 'default' ? 'direct' : 'threadMode',
769
- additionalInstructionSections: buildPlanTurnInstructionSections(planTurn),
770
- extraMessages: [buildPlanTurnPromptMessage(planTurn)],
771
- includeExecutionPlanTools: false,
772
- extraTools: { [SUBMIT_PLAN_TURN_RESULT_TOOL_NAME]: submitPlanTurnNodeResultTool },
773
- filterTools: (tools) => applyPlanTurnToolPolicy(tools, planTurn.nodeSpec),
774
- metadataPatch: { trigger: 'plan-turn', planRunId: planTurn.runId, planNodeId: planTurn.nodeId },
775
- }),
776
- 'Failed to execute plan-turn visible agent.',
777
- ).pipe(Effect.withSpan('ThreadTurnPreparation.executePlanTurn'))
814
+ yield* runVisibleAgent({
815
+ agentId: planTurn.nodeSpec.owner.ref,
816
+ mode: thread.type === 'default' ? 'direct' : 'threadMode',
817
+ additionalInstructionSections: buildPlanTurnInstructionSections(planTurn),
818
+ extraMessages: [buildPlanTurnPromptMessage(planTurn)],
819
+ includeExecutionPlanTools: false,
820
+ extraTools: { [SUBMIT_PLAN_TURN_RESULT_TOOL_NAME]: submitPlanTurnNodeResultTool },
821
+ filterTools: (tools) => applyPlanTurnToolPolicy(tools, planTurn.nodeSpec),
822
+ metadataPatch: { trigger: 'plan-turn', planRunId: planTurn.runId, planNodeId: planTurn.nodeId },
823
+ }).pipe(
824
+ Effect.mapError((cause) =>
825
+ toThreadTurnPreparationError('Failed to execute plan-turn visible agent.', cause),
826
+ ),
827
+ Effect.withSpan('ThreadTurnPreparation.executePlanTurn'),
828
+ )
778
829
  } else if (thread.type === 'default') {
779
830
  if (!thread.agentId) {
780
831
  return yield* new ThreadTurnError({
@@ -783,10 +834,10 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
783
834
  })
784
835
  }
785
836
  const directAgentId = thread.agentId
786
- yield* effectTryPromise(
787
- () => runVisibleAgent({ agentId: directAgentId, mode: 'direct' }),
788
- 'Failed to execute direct thread agent.',
789
- ).pipe(Effect.withSpan('ThreadTurnPreparation.executeDirectThread'))
837
+ yield* runVisibleAgent({ agentId: directAgentId, mode: 'direct' }).pipe(
838
+ Effect.mapError((cause) => toThreadTurnPreparationError('Failed to execute direct thread agent.', cause)),
839
+ Effect.withSpan('ThreadTurnPreparation.executeDirectThread'),
840
+ )
790
841
  } else {
791
842
  const wsMembers = (thread as { members?: string[] }).members ?? []
792
843
  const members = wsMembers.length > 0 ? wsMembers : [...agentConfig.roster]
@@ -799,10 +850,11 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
799
850
  .map((m) => `${m.role}: ${extractMessageText(m).slice(0, 200)}`)
800
851
  .join('\n')
801
852
 
802
- const triageResult = yield* effectTryPromise(
803
- () =>
853
+ const triageResult = yield* Effect.tryPromise({
854
+ try: () =>
804
855
  triageThreadMessage({
805
856
  agentConfig,
857
+ aiGatewayModels: deps.aiGatewayModels,
806
858
  threadTitle: thread.title,
807
859
  members,
808
860
  messageText,
@@ -811,8 +863,8 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
811
863
  shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
812
864
  routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
813
865
  }),
814
- 'Failed to triage group thread message.',
815
- ).pipe(Effect.withSpan('ThreadTurnPreparation.routeGroupThread'))
866
+ catch: (cause) => toThreadTurnPreparationError('Failed to triage group thread message.', cause),
867
+ }).pipe(Effect.withSpan('ThreadTurnPreparation.routeGroupThread'))
816
868
  yield* failIfRunAborted()
817
869
 
818
870
  const runGroupAgent = (agentId: string, options?: { routingContext?: string }) => {
@@ -833,27 +885,32 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
833
885
  }
834
886
 
835
887
  if (!triageResult) {
836
- yield* effectTryPromise(
837
- () => runGroupAgent(fallbackAgentId),
838
- 'Failed to execute fallback group agent.',
839
- ).pipe(Effect.withSpan('ThreadTurnPreparation.executeFallbackGroupAgent'))
888
+ yield* runGroupAgent(fallbackAgentId).pipe(
889
+ Effect.mapError((cause) =>
890
+ toThreadTurnPreparationError('Failed to execute fallback group agent.', cause),
891
+ ),
892
+ Effect.withSpan('ThreadTurnPreparation.executeFallbackGroupAgent'),
893
+ )
840
894
  yield* failIfRunAborted()
841
895
  writeMultiAgentEvent(runParams.writer, { phase: 'complete' })
842
896
  } else {
843
897
  const respondedAgents: string[] = []
844
- let lastResponse = yield* effectTryPromise(
845
- () => runGroupAgent(triageResult.agentId, { routingContext: triageResult.routingContext }),
846
- 'Failed to execute routed group agent.',
847
- ).pipe(Effect.withSpan('ThreadTurnPreparation.executeRoutedGroupAgent'))
898
+ let lastResponse = yield* runGroupAgent(triageResult.agentId, {
899
+ routingContext: triageResult.routingContext,
900
+ }).pipe(
901
+ Effect.mapError((cause) => toThreadTurnPreparationError('Failed to execute routed group agent.', cause)),
902
+ Effect.withSpan('ThreadTurnPreparation.executeRoutedGroupAgent'),
903
+ )
848
904
  respondedAgents.push(triageResult.agentId)
849
905
  yield* failIfRunAborted()
850
906
 
851
907
  while (respondedAgents.length < 3) {
852
908
  const lastResponseText = extractMessageText(lastResponse).slice(0, 500)
853
- const checkResult = yield* effectTryPromise(
854
- () =>
909
+ const checkResult = yield* Effect.tryPromise({
910
+ try: () =>
855
911
  checkForNextAgent({
856
912
  agentConfig,
913
+ aiGatewayModels: deps.aiGatewayModels,
857
914
  threadTitle: thread.title,
858
915
  members,
859
916
  messageText,
@@ -863,8 +920,8 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
863
920
  shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
864
921
  routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
865
922
  }),
866
- 'Failed to select the next group agent.',
867
- ).pipe(Effect.withSpan('ThreadTurnPreparation.selectNextGroupAgent'))
923
+ catch: (cause) => toThreadTurnPreparationError('Failed to select the next group agent.', cause),
924
+ }).pipe(Effect.withSpan('ThreadTurnPreparation.selectNextGroupAgent'))
868
925
  yield* failIfRunAborted()
869
926
 
870
927
  if (checkResult.done || !checkResult.agentId) break
@@ -894,10 +951,14 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
894
951
  threadTurnMessageContext.appendMessages([bridgeMessage])
895
952
  yield* failIfRunAborted()
896
953
 
897
- lastResponse = yield* effectTryPromise(
898
- () => runGroupAgent(checkResult.agentId, { routingContext: checkResult.routingContext ?? undefined }),
899
- 'Failed to execute follow-up group agent.',
900
- ).pipe(Effect.withSpan('ThreadTurnPreparation.executeFollowUpGroupAgent'))
954
+ lastResponse = yield* runGroupAgent(checkResult.agentId, {
955
+ routingContext: checkResult.routingContext ?? undefined,
956
+ }).pipe(
957
+ Effect.mapError((cause) =>
958
+ toThreadTurnPreparationError('Failed to execute follow-up group agent.', cause),
959
+ ),
960
+ Effect.withSpan('ThreadTurnPreparation.executeFollowUpGroupAgent'),
961
+ )
901
962
  respondedAgents.push(checkResult.agentId)
902
963
  yield* failIfRunAborted()
903
964
  writeMultiAgentEvent(runParams.writer, {
@@ -932,10 +993,14 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
932
993
  contextSize: CONTEXT_WINDOW_TOKENS,
933
994
  }),
934
995
  enqueueCompaction: () =>
935
- deps.contextCompactionQueue.enqueueContextCompaction({
936
- domain: 'thread',
937
- entityId: threadIdString,
938
- contextSize: CONTEXT_WINDOW_TOKENS,
996
+ Effect.tryPromise({
997
+ try: () =>
998
+ deps.contextCompactionQueue.enqueueContextCompaction({
999
+ domain: 'thread',
1000
+ entityId: threadIdString,
1001
+ contextSize: CONTEXT_WINDOW_TOKENS,
1002
+ }),
1003
+ catch: (cause) => new TurnLifecycleError({ message: getErrorMessage(cause), cause }),
939
1004
  }),
940
1005
  unregisterRun: () => undefined,
941
1006
  clearActiveRunId: (runId) =>
@@ -965,32 +1030,36 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
965
1030
  )
966
1031
 
967
1032
  if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
968
- yield* effectTryPromise(
969
- () =>
970
- runPostTurnSideEffects({
971
- thread,
972
- threadRef,
973
- orgRef,
974
- userRef,
975
- userName,
976
- orgIdString,
977
- threadIdString,
978
- onboardingActive,
979
- workspace,
980
- allAssistantMessages,
981
- referenceUserMessage,
982
- referenceUserMessageId: referenceUserMessage?.id ?? '',
983
- loadRecentHistory,
984
- listReadableUploads: () => threadTurnMessageContext.listReadableUploads(),
985
- memoryBlock,
986
- visibleThreadAgentId,
987
- defaultLeadAgentId,
988
- latestThreadRecord,
989
- isUserTurn: params.kind === 'userTurn',
990
- agentDisplayNamesById: agentIdentityOverrides.displayNamesById,
991
- }),
992
- 'Failed to run post-turn side effects.',
993
- ).pipe(Effect.withSpan('ThreadTurnPreparation.runPostTurnSideEffects'))
1033
+ yield* runPostTurnSideEffects({
1034
+ thread,
1035
+ threadRef,
1036
+ orgRef,
1037
+ userRef,
1038
+ userName,
1039
+ orgIdString,
1040
+ threadIdString,
1041
+ onboardingActive,
1042
+ workspace,
1043
+ allAssistantMessages,
1044
+ referenceUserMessage,
1045
+ referenceUserMessageId: referenceUserMessage?.id ?? '',
1046
+ loadRecentHistory,
1047
+ listReadableUploads: () => threadTurnMessageContext.listReadableUploads(),
1048
+ memoryBlock,
1049
+ visibleThreadAgentId,
1050
+ defaultLeadAgentId,
1051
+ latestThreadRecord,
1052
+ isUserTurn: params.kind === 'userTurn',
1053
+ agentDisplayNamesById: agentIdentityOverrides.displayNamesById,
1054
+ }).pipe(
1055
+ Effect.provideService(RecentActivityServiceTag, recentActivityService),
1056
+ Effect.provideService(ThreadServiceTag, threadService),
1057
+ Effect.provideService(AgentConfigServiceTag, agentConfig),
1058
+ Effect.provideService(RuntimeAdaptersServiceTag, runtimeAdapters),
1059
+ Effect.provideService(LotaQueuesServiceTag, deps.queues),
1060
+ Effect.mapError((cause) => toThreadTurnPreparationError('Failed to run post-turn side effects.', cause)),
1061
+ Effect.withSpan('ThreadTurnPreparation.runPostTurnSideEffects'),
1062
+ )
994
1063
  }
995
1064
 
996
1065
  yield* Effect.sync(() => {
@@ -1004,8 +1073,11 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1004
1073
  ),
1005
1074
  ),
1006
1075
  ).pipe(
1076
+ Effect.provideService(BackgroundWorkServiceTag, deps.backgroundWork),
1077
+ Effect.provideService(AgentFactoryServiceTag, agentFactoryConfig),
1078
+ Effect.provideService(AgentConfigServiceTag, agentConfig),
1007
1079
  Effect.mapError((error) =>
1008
- error._tag === 'ServiceError'
1080
+ error._tag === ERROR_TAGS.ServiceError
1009
1081
  ? new ThreadTurnPreparationError({ message: error.message, cause: error.cause })
1010
1082
  : error,
1011
1083
  ),
@@ -1030,8 +1102,8 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1030
1102
  // streamed turn closes after persistence/finalization instead of waiting on
1031
1103
  // onboarding/map side effects in the request path.
1032
1104
  void runPromiseWithCurrentContext(
1033
- effectTryPromise(
1034
- () =>
1105
+ Effect.tryPromise({
1106
+ try: () =>
1035
1107
  afterTurn({
1036
1108
  thread,
1037
1109
  threadRef,
@@ -1044,8 +1116,8 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1044
1116
  latestThreadRecord,
1045
1117
  context: buildContextResult,
1046
1118
  }),
1047
- 'Failed to run afterTurn hook.',
1048
- ).pipe(
1119
+ catch: (cause) => toThreadTurnPreparationError('Failed to run afterTurn hook.', cause),
1120
+ }).pipe(
1049
1121
  Effect.withSpan('ThreadTurnPreparation.afterTurnHook'),
1050
1122
  Effect.catch((error) =>
1051
1123
  Effect.sync(() => {
@@ -1061,7 +1133,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1061
1133
  const run = (writer?: UIMessageStreamWriter<ChatMessage>) => {
1062
1134
  const serverRunId = Bun.randomUUIDv7()
1063
1135
 
1064
- const runEffect =
1136
+ const runEffect: ThreadTurnRunEffect =
1065
1137
  params.kind === 'planTurn'
1066
1138
  ? Effect.gen(function* () {
1067
1139
  const runAbort = createServerRunAbortController(
@@ -1071,25 +1143,30 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1071
1143
  const runResult = yield* executeRun({ serverRunId, runAbort, writer })
1072
1144
  return runResult ?? toPreparedThreadTurnResult()
1073
1145
  })
1074
- : threadService.withActiveRunLease(threadRef, (leaseAbortSignal) =>
1075
- Effect.gen(function* () {
1076
- const runAbort = createServerRunAbortController(
1077
- [params.abortSignal, leaseAbortSignal].filter((signal): signal is AbortSignal => Boolean(signal)),
1078
- )
1079
- yield* Effect.annotateCurrentSpan('serverRunId', serverRunId)
1080
- const runResult = yield* chatRunRegistry.trackRunEffect(
1081
- serverRunId,
1082
- runAbort.controller,
1083
- executeRun({ serverRunId, runAbort, writer }),
1084
- )
1085
- return runResult ?? toPreparedThreadTurnResult()
1086
- }),
1087
- )
1146
+ : threadService
1147
+ .withActiveRunLease(threadRef, (leaseAbortSignal) =>
1148
+ Effect.gen(function* () {
1149
+ const runAbort = createServerRunAbortController(
1150
+ [params.abortSignal, leaseAbortSignal].filter((signal): signal is AbortSignal => Boolean(signal)),
1151
+ )
1152
+ yield* Effect.annotateCurrentSpan('serverRunId', serverRunId)
1153
+ const runResult = yield* chatRunRegistry.trackRunEffect(
1154
+ serverRunId,
1155
+ runAbort.controller,
1156
+ executeRun({ serverRunId, runAbort, writer }),
1157
+ )
1158
+ return runResult ?? toPreparedThreadTurnResult()
1159
+ }),
1160
+ )
1161
+ .pipe(
1162
+ Effect.mapError((error) =>
1163
+ error._tag === ERROR_TAGS.ActiveThreadRunConflictError
1164
+ ? new ThreadTurnError({ message: error.message, reason: 'conflict' })
1165
+ : new ThreadTurnPreparationError({ message: 'Failed to acquire thread run lease.', cause: error }),
1166
+ ),
1167
+ )
1088
1168
 
1089
- return runEffect.pipe(
1090
- Effect.withSpan('ThreadTurnPreparation.executeRun'),
1091
- Effect.provide(currentContext),
1092
- ) as Effect.Effect<PreparedThreadTurnResult, DatabaseError | ThreadTurnError | ThreadTurnPreparationError, never>
1169
+ return runEffect.pipe(Effect.withSpan('ThreadTurnPreparation.executeRun'), Effect.provide(currentContext))
1093
1170
  }
1094
1171
 
1095
1172
  return { originalMessages, run }
@@ -1097,38 +1174,62 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1097
1174
 
1098
1175
  interface ThreadTurnPreparationDeps {
1099
1176
  attachment: ReturnType<typeof makeAttachmentService>
1177
+ agentConfig: ResolvedAgentConfig
1178
+ agentFactoryConfig: Context.Service.Shape<typeof AgentFactoryServiceTag>
1179
+ runtimeAdapters: Context.Service.Shape<typeof RuntimeAdaptersServiceTag>
1180
+ turnHooks: Context.Service.Shape<typeof TurnHooksServiceTag>
1181
+ backgroundWork: Context.Service.Shape<typeof BackgroundWorkServiceTag>
1100
1182
  chatRunRegistry: Context.Service.Shape<typeof ChatRunRegistryTag>
1101
1183
  compactionCoordination: Context.Service.Shape<typeof CompactionCoordinationTag>
1102
1184
  executionPlan: ReturnType<typeof makeExecutionPlanService>
1103
1185
  learnedSkill: ReturnType<typeof makeLearnedSkillService>
1104
1186
  memory: ReturnType<typeof createMemoryService>
1105
1187
  planRun: ReturnType<typeof makePlanRunService>
1188
+ recentActivity: Context.Service.Shape<typeof RecentActivityServiceTag>
1106
1189
  threadMessage: ReturnType<typeof makeThreadMessageService>
1107
1190
  thread: ReturnType<typeof makeThreadService>
1191
+ queues: Context.Service.Shape<typeof LotaQueuesServiceTag>
1108
1192
  helperModelRuntime: HelperModelRuntime
1193
+ aiGatewayModels: AiGatewayModels
1109
1194
  contextCompactionQueue: ContextCompactionQueueRuntime
1110
1195
  titleGenerationQueue: TitleGenerationQueueRuntime
1196
+ runPromise: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>
1197
+ provideCurrentContext: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, never>
1111
1198
  }
1112
1199
 
1113
1200
  export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps) {
1114
1201
  const runtimeDeps: ThreadTurnPreparationRuntimeDeps = {
1115
1202
  attachmentService: deps.attachment,
1203
+ agentConfig: deps.agentConfig,
1204
+ agentFactoryConfig: deps.agentFactoryConfig,
1205
+ runtimeAdapters: deps.runtimeAdapters,
1206
+ turnHooks: deps.turnHooks,
1207
+ backgroundWork: deps.backgroundWork,
1116
1208
  chatRunRegistry: deps.chatRunRegistry,
1117
1209
  compactionCoordination: deps.compactionCoordination,
1118
1210
  executionPlanService: deps.executionPlan,
1119
1211
  learnedSkillService: deps.learnedSkill,
1120
1212
  memoryService: deps.memory,
1121
1213
  planRunService: deps.planRun,
1214
+ recentActivityService: deps.recentActivity,
1122
1215
  threadMessageService: deps.threadMessage,
1123
1216
  threadService: deps.thread,
1124
1217
  contextCompactionRuntime: createWiredContextCompactionRuntime({
1125
1218
  helperModelRuntime: deps.helperModelRuntime,
1219
+ aiGatewayModels: deps.aiGatewayModels,
1126
1220
  now: nowEpochMillis,
1127
1221
  randomId: () => Bun.randomUUIDv7(),
1128
1222
  }),
1223
+ queues: deps.queues,
1129
1224
  contextCompactionQueue: deps.contextCompactionQueue,
1130
1225
  titleGenerationQueue: deps.titleGenerationQueue,
1226
+ aiGatewayModels: deps.aiGatewayModels,
1227
+ runPromise: deps.runPromise,
1131
1228
  }
1229
+ const closeContext = <A, E extends ThreadTurnPreparationTaggedError, R>(
1230
+ effect: Effect.Effect<A, E, R>,
1231
+ ): Effect.Effect<A, ThreadTurnPreparationSurfaceError, never> =>
1232
+ deps.provideCurrentContext(effect).pipe(Effect.mapError(normalizeThreadTurnPreparationError))
1132
1233
  const annotateTurnSpans = <A, E, R>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E, R>) =>
1133
1234
  effect.pipe(
1134
1235
  Effect.annotateSpans(
@@ -1151,10 +1252,10 @@ export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps
1151
1252
 
1152
1253
  return {
1153
1254
  prepareThreadRunCoreEffect(params: ThreadRunCoreParams) {
1154
- return annotateTurnSpans(params, prepareThreadRunCoreEffect(runtimeDeps, params))
1255
+ return closeContext(annotateTurnSpans(params, prepareThreadRunCoreEffect(runtimeDeps, params)))
1155
1256
  },
1156
1257
  prepareThreadRunCore(params: ThreadRunCoreParams) {
1157
- return annotateTurnSpans(params, prepareThreadRunCoreEffect(runtimeDeps, params))
1258
+ return closeContext(annotateTurnSpans(params, prepareThreadRunCoreEffect(runtimeDeps, params)))
1158
1259
  },
1159
1260
  } as const
1160
1261
  }
@@ -1167,29 +1268,50 @@ export class ThreadTurnPreparationServiceTag extends Context.Service<
1167
1268
  export const ThreadTurnPreparationServiceLive = Layer.effect(
1168
1269
  ThreadTurnPreparationServiceTag,
1169
1270
  Effect.gen(function* () {
1271
+ const currentContext = yield* Effect.context()
1272
+ const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
1273
+ effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
1170
1274
  const attachment = yield* AttachmentServiceTag
1275
+ const agentConfig = yield* AgentConfigServiceTag
1276
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
1277
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
1278
+ const turnHooks = yield* TurnHooksServiceTag
1279
+ const backgroundWork = yield* BackgroundWorkServiceTag
1171
1280
  const chatRunRegistry = yield* ChatRunRegistryTag
1172
1281
  const compactionCoordination = yield* CompactionCoordinationTag
1173
1282
  const executionPlan = yield* ExecutionPlanServiceTag
1174
1283
  const learnedSkill = yield* LearnedSkillServiceTag
1175
1284
  const memory = yield* MemoryServiceTag
1176
1285
  const planRun = yield* PlanRunServiceTag
1286
+ const recentActivity = yield* RecentActivityServiceTag
1177
1287
  const threadMessage = yield* ThreadMessageServiceTag
1178
1288
  const thread = yield* ThreadServiceTag
1179
1289
  const queues = yield* LotaQueuesServiceTag
1290
+ const aiGatewayModels = yield* AiGatewayModelsTag
1291
+ const bridge = yield* RuntimeBridgeTag
1180
1292
  return makeThreadTurnPreparationService({
1181
1293
  attachment,
1294
+ agentConfig,
1295
+ agentFactoryConfig,
1296
+ runtimeAdapters,
1297
+ turnHooks,
1298
+ backgroundWork,
1182
1299
  chatRunRegistry,
1183
1300
  compactionCoordination,
1184
1301
  executionPlan,
1185
1302
  learnedSkill,
1186
1303
  memory,
1187
1304
  planRun,
1305
+ recentActivity,
1188
1306
  threadMessage,
1189
1307
  thread,
1308
+ queues,
1190
1309
  helperModelRuntime: yield* HelperModelTag,
1310
+ aiGatewayModels,
1191
1311
  contextCompactionQueue: queues.contextCompaction,
1192
1312
  titleGenerationQueue: queues.titleGeneration,
1313
+ runPromise: bridge.runPromise,
1314
+ provideCurrentContext,
1193
1315
  })
1194
1316
  }),
1195
1317
  )