@lota-sdk/core 0.4.10 → 0.4.11

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 (108) hide show
  1. package/package.json +2 -2
  2. package/src/ai-gateway/ai-gateway.ts +149 -95
  3. package/src/ai-gateway/index.ts +16 -1
  4. package/src/config/agent-defaults.ts +4 -120
  5. package/src/config/logger.ts +18 -34
  6. package/src/config/thread-defaults.ts +1 -18
  7. package/src/create-runtime.ts +90 -28
  8. package/src/db/base.service.ts +30 -38
  9. package/src/db/service.ts +489 -545
  10. package/src/effect/index.ts +0 -2
  11. package/src/effect/layers.ts +6 -13
  12. package/src/embeddings/provider.ts +2 -7
  13. package/src/index.ts +4 -5
  14. package/src/queues/autonomous-job.queue.ts +159 -113
  15. package/src/queues/context-compaction.queue.ts +39 -25
  16. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  17. package/src/queues/document-processor.queue.ts +5 -3
  18. package/src/queues/index.ts +1 -0
  19. package/src/queues/memory-consolidation.queue.ts +79 -53
  20. package/src/queues/organization-learning.queue.ts +63 -39
  21. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  22. package/src/queues/plan-scheduler.queue.ts +100 -84
  23. package/src/queues/post-chat-memory.queue.ts +55 -33
  24. package/src/queues/queue-factory.ts +40 -41
  25. package/src/queues/queues.service.ts +61 -0
  26. package/src/queues/title-generation.queue.ts +42 -31
  27. package/src/redis/org-memory-lock.ts +24 -9
  28. package/src/redis/redis-lease-lock.ts +8 -1
  29. package/src/runtime/agent-identity-overrides.ts +7 -3
  30. package/src/runtime/agent-runtime-policy.ts +9 -4
  31. package/src/runtime/agent-stream-helpers.ts +9 -4
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  33. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  34. package/src/runtime/domain-layer.ts +15 -4
  35. package/src/runtime/execution-plan-visibility.ts +5 -2
  36. package/src/runtime/graph-designer.ts +0 -22
  37. package/src/runtime/index.ts +1 -0
  38. package/src/runtime/indexed-repositories-policy.ts +2 -6
  39. package/src/runtime/plugin-resolution.ts +29 -12
  40. package/src/runtime/post-turn-side-effects.ts +139 -141
  41. package/src/runtime/runtime-config.ts +0 -6
  42. package/src/runtime/runtime-extensions.ts +0 -54
  43. package/src/runtime/runtime-lifecycle.ts +4 -4
  44. package/src/runtime/runtime-services.ts +122 -53
  45. package/src/runtime/runtime-worker-registry.ts +113 -30
  46. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  47. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  48. package/src/runtime/social-chat/social-chat.ts +35 -20
  49. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  50. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  51. package/src/runtime/thread-chat-helpers.ts +18 -9
  52. package/src/runtime/thread-turn-context.ts +7 -47
  53. package/src/runtime/turn-lifecycle.ts +6 -14
  54. package/src/services/agent-activity.service.ts +168 -175
  55. package/src/services/agent-executor.service.ts +35 -16
  56. package/src/services/attachment.service.ts +4 -70
  57. package/src/services/autonomous-job.service.ts +53 -61
  58. package/src/services/context-compaction.service.ts +7 -9
  59. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  60. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  61. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  62. package/src/services/global-orchestrator.service.ts +18 -7
  63. package/src/services/graph-full-routing.ts +7 -6
  64. package/src/services/memory/memory-conversation.ts +10 -5
  65. package/src/services/memory/memory.service.ts +11 -8
  66. package/src/services/ownership-dispatcher.service.ts +16 -5
  67. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  68. package/src/services/plan/plan-agent-query.service.ts +12 -8
  69. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  70. package/src/services/plan/plan-cycle.service.ts +7 -45
  71. package/src/services/plan/plan-deadline.service.ts +28 -17
  72. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  73. package/src/services/plan/plan-executor-context.ts +2 -0
  74. package/src/services/plan/plan-executor-graph.ts +366 -391
  75. package/src/services/plan/plan-executor.service.ts +13 -91
  76. package/src/services/plan/plan-scheduler.service.ts +62 -49
  77. package/src/services/plan/plan-transaction-events.ts +1 -1
  78. package/src/services/recent-activity-title.service.ts +6 -2
  79. package/src/services/thread/thread-bootstrap.ts +11 -9
  80. package/src/services/thread/thread-message.service.ts +6 -5
  81. package/src/services/thread/thread-turn-execution.ts +86 -82
  82. package/src/services/thread/thread-turn-preparation.service.ts +47 -24
  83. package/src/services/thread/thread-turn-streaming.ts +20 -25
  84. package/src/services/thread/thread-turn.ts +25 -44
  85. package/src/services/thread/thread.service.ts +21 -6
  86. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  87. package/src/system-agents/thread-router.agent.ts +23 -20
  88. package/src/tools/execution-plan.tool.ts +8 -3
  89. package/src/tools/fetch-webpage.tool.ts +10 -9
  90. package/src/tools/firecrawl-client.ts +0 -15
  91. package/src/tools/remember-memory.tool.ts +3 -6
  92. package/src/tools/research-topic.tool.ts +12 -3
  93. package/src/tools/search-web.tool.ts +10 -9
  94. package/src/tools/search.tool.ts +4 -5
  95. package/src/tools/team-think.tool.ts +139 -121
  96. package/src/workers/bootstrap.ts +9 -10
  97. package/src/workers/memory-consolidation.worker.ts +4 -1
  98. package/src/workers/organization-learning.worker.ts +15 -2
  99. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  100. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  101. package/src/workers/skill-extraction.runner.ts +13 -15
  102. package/src/workers/worker-utils.ts +6 -18
  103. package/src/effect/awaitable-effect.ts +0 -96
  104. package/src/effect/runtime-ref.ts +0 -25
  105. package/src/effect/runtime.ts +0 -46
  106. package/src/redis/runtime-connection.ts +0 -20
  107. package/src/runtime/runtime-accessors.ts +0 -92
  108. package/src/runtime/runtime-token.ts +0 -47
@@ -3,10 +3,10 @@ import { SUBMIT_PLAN_TURN_RESULT_TOOL_NAME, toTimestamp, withMessageCreatedAt }
3
3
  import type { ToolSet, UIMessageStreamWriter } from 'ai'
4
4
  import { Schema, Effect } from 'effect'
5
5
 
6
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
6
7
  import { aiLogger } from '../../config/logger'
7
8
  import type { RecordIdRef } from '../../db/record-id'
8
9
  import { effectTryMaybeAsync, effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
9
- import { runPromise } from '../../effect/runtime'
10
10
  import type { readRuntimeAgentIdentityOverrides } from '../../runtime/agent-identity-overrides'
11
11
  import { resolveRuntimeAgentDisplayName } from '../../runtime/agent-identity-overrides'
12
12
  import { OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES } from '../../runtime/agent-runtime-policy'
@@ -67,6 +67,7 @@ interface VisibleAgentRunParams {
67
67
  }
68
68
 
69
69
  interface VisibleAgentRunnerDeps<TBuildTurnToolParams> {
70
+ agentConfig: ResolvedAgentConfig
70
71
  agentFactoryConfig: { buildAgentTools: (params: TBuildTurnToolParams) => Promise<ToolSet> | ToolSet }
71
72
  buildTurnToolParams: (params: {
72
73
  agentId: string
@@ -100,10 +101,10 @@ export function createThreadTurnVisibleAgentRunner<TBuildTurnToolParams>(
100
101
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
101
102
  ) {}
102
103
 
103
- const effectTryPromise = <A>(
104
- evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
104
+ const effectTryPromise = <A, R = never>(
105
+ evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown, R>,
105
106
  message: string,
106
- ): Effect.Effect<A, ThreadTurnExecutionError> =>
107
+ ): Effect.Effect<A, ThreadTurnExecutionError, R> =>
107
108
  effectTryPromiseShared(evaluate, (error) => new ThreadTurnExecutionError({ message, cause: error }))
108
109
 
109
110
  function effectFromMaybePromise<A>(
@@ -130,14 +131,13 @@ export function createThreadTurnVisibleAgentRunner<TBuildTurnToolParams>(
130
131
  )
131
132
 
132
133
  const createObserver = (agentId: string) => ({
133
- run: <T>(fn: () => T | Promise<T>) =>
134
- runPromise(
135
- effectTryMaybeAsync(
136
- fn,
137
- (error) =>
138
- new ThreadTurnExecutionError({ message: `Agent observer run failed (agent=${agentId}).`, cause: error }),
139
- ),
140
- ),
134
+ run: async <T>(fn: () => T | Promise<T>): Promise<T> => {
135
+ try {
136
+ return await fn()
137
+ } catch (error) {
138
+ throw new ThreadTurnExecutionError({ message: `Agent observer run failed (agent=${agentId}).`, cause: error })
139
+ }
140
+ },
141
141
  recordError: (error: unknown) => {
142
142
  aiLogger.error`Agent run failed (agent=${agentId}): ${error}`
143
143
  },
@@ -151,54 +151,53 @@ export function createThreadTurnVisibleAgentRunner<TBuildTurnToolParams>(
151
151
  agentId: string,
152
152
  agentName: string,
153
153
  metadataPatch?: NonNullable<MessageMetadata>,
154
- ): Promise<ChatMessage> =>
155
- runPromise(
156
- Effect.gen(function* () {
157
- yield* failIfRunAborted()
158
-
159
- const toCommittedAssistantMessage = (
160
- message: ChatMessage,
161
- resolvedAgentId: string,
162
- resolvedAgentName: string,
163
- patch?: NonNullable<MessageMetadata>,
164
- ) =>
165
- withMessageCreatedAt(
166
- {
167
- ...message,
168
- metadata: {
169
- ...message.metadata,
170
- ...buildAgentMetadataPatch(resolvedAgentId, resolvedAgentName),
171
- ...patch,
172
- },
173
- },
174
- toTimestamp(message.metadata?.createdAt) ?? nowEpochMillis(),
175
- )
176
-
177
- const committedConsultMessages = collectCompletedConsultTeamMessages({ responseMessage: response }).flatMap(
178
- (consultMessage) => {
179
- const consultAgentId = readOptionalString(consultMessage.metadata?.agentId)
180
- const consultAgentName = readOptionalString(consultMessage.metadata?.agentName)
181
- if (!consultAgentId || !consultAgentName) {
182
- return []
183
- }
184
-
185
- return [toCommittedAssistantMessage(consultMessage, consultAgentId, consultAgentName)]
154
+ ) =>
155
+ Effect.gen(function* () {
156
+ // Persist the response even if the run is being interrupted (e.g., lease
157
+ // refresh was rejected between the final stream chunk and this commit).
158
+ // The LLM tokens have already been consumed; the message must reach the
159
+ // thread history so callers and post-turn side effects observe the
160
+ // completed turn. `Effect.uninterruptible` shields the persist from the
161
+ // surrounding interrupt without blocking later steps from handling it.
162
+ const toCommittedAssistantMessage = (
163
+ message: ChatMessage,
164
+ resolvedAgentId: string,
165
+ resolvedAgentName: string,
166
+ patch?: NonNullable<MessageMetadata>,
167
+ ) =>
168
+ withMessageCreatedAt(
169
+ {
170
+ ...message,
171
+ metadata: { ...message.metadata, ...buildAgentMetadataPatch(resolvedAgentId, resolvedAgentName), ...patch },
186
172
  },
173
+ toTimestamp(message.metadata?.createdAt) ?? nowEpochMillis(),
187
174
  )
188
175
 
189
- const committed = toCommittedAssistantMessage(response, agentId, agentName, metadataPatch)
190
- const messagesToPersist = [...committedConsultMessages, committed]
191
- yield* deps.threadMessageService.upsertMessagesEffect({ threadId: deps.threadRef, messages: messagesToPersist })
176
+ const committedConsultMessages = collectCompletedConsultTeamMessages({ responseMessage: response }).flatMap(
177
+ (consultMessage) => {
178
+ const consultAgentId = readOptionalString(consultMessage.metadata?.agentId)
179
+ const consultAgentName = readOptionalString(consultMessage.metadata?.agentName)
180
+ if (!consultAgentId || !consultAgentName) {
181
+ return []
182
+ }
192
183
 
193
- for (const persistedMessage of messagesToPersist) {
194
- deps.threadTurnMessageContext.appendMessages([persistedMessage])
195
- deps.onPersistedMessage(persistedMessage)
196
- }
197
- yield* failIfRunAborted()
184
+ return [toCommittedAssistantMessage(consultMessage, consultAgentId, consultAgentName)]
185
+ },
186
+ )
198
187
 
199
- return committed
200
- }),
201
- )
188
+ const committed = toCommittedAssistantMessage(response, agentId, agentName, metadataPatch)
189
+ const messagesToPersist = [...committedConsultMessages, committed]
190
+ yield* Effect.uninterruptible(
191
+ deps.threadMessageService.upsertMessagesEffect({ threadId: deps.threadRef, messages: messagesToPersist }),
192
+ )
193
+
194
+ for (const persistedMessage of messagesToPersist) {
195
+ deps.threadTurnMessageContext.appendMessages([persistedMessage])
196
+ deps.onPersistedMessage(persistedMessage)
197
+ }
198
+
199
+ return committed
200
+ })
202
201
 
203
202
  const runVisibleAgentEffect = Effect.fn('ThreadTurnExecution.runVisibleAgent')(function* (
204
203
  runParams: VisibleAgentRunParams,
@@ -243,36 +242,41 @@ export function createThreadTurnVisibleAgentRunner<TBuildTurnToolParams>(
243
242
  ).pipe(Effect.withSpan('ThreadTurnExecution.streamVisibleAgent'))
244
243
  deps.setMemoryBlock(runMemoryBlock)
245
244
 
246
- return yield* effectTryPromise(
247
- () =>
248
- commitAssistantResponse(
249
- responseMessage,
250
- runParams.agentId,
251
- resolveRuntimeAgentDisplayName(deps.agentIdentityOverrides, runParams.agentId),
252
- runParams.metadataPatch,
253
- ),
254
- `Failed to commit assistant response for ${runParams.agentId}.`,
255
- ).pipe(Effect.withSpan('ThreadTurnExecution.commitAssistantResponse'))
245
+ // Commit the response under `uninterruptible` so a late lease-lost or
246
+ // stop signal arriving after the stream completed still writes the
247
+ // already-generated assistant message into the thread history. Without
248
+ // this, a race between stream completion and lease refresh rejection
249
+ // swallows the response without any error trace.
250
+ return yield* Effect.uninterruptible(
251
+ effectTryPromise(
252
+ () =>
253
+ commitAssistantResponse(
254
+ responseMessage,
255
+ runParams.agentId,
256
+ resolveRuntimeAgentDisplayName(deps.agentConfig, deps.agentIdentityOverrides, runParams.agentId),
257
+ runParams.metadataPatch,
258
+ ),
259
+ `Failed to commit assistant response for ${runParams.agentId}.`,
260
+ ).pipe(Effect.withSpan('ThreadTurnExecution.commitAssistantResponse')),
261
+ )
256
262
  })
257
263
 
258
- const runVisibleAgent = (runParams: VisibleAgentRunParams): Promise<ChatMessage> =>
259
- runPromise(
260
- runVisibleAgentEffect(runParams).pipe(
261
- Effect.annotateSpans(
262
- compactSpanAttributes({
263
- ...buildThreadTurnSpanAttributes({
264
- threadRef: deps.threadRef,
265
- orgRef: deps.streamCtx.orgRef,
266
- userRef: deps.streamCtx.userRef,
267
- agentId: runParams.agentId,
268
- threadType: deps.streamCtx.thread.type,
269
- mode: runParams.mode,
270
- }),
271
- includeExecutionPlanTools: runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedThreadMode',
272
- extraMessageCount: runParams.extraMessages?.length ?? 0,
273
- extraToolCount: runParams.extraTools ? Object.keys(runParams.extraTools).length : 0,
264
+ const runVisibleAgent = (runParams: VisibleAgentRunParams) =>
265
+ runVisibleAgentEffect(runParams).pipe(
266
+ Effect.annotateSpans(
267
+ compactSpanAttributes({
268
+ ...buildThreadTurnSpanAttributes({
269
+ threadRef: deps.threadRef,
270
+ orgRef: deps.streamCtx.orgRef,
271
+ userRef: deps.streamCtx.userRef,
272
+ agentId: runParams.agentId,
273
+ threadType: deps.streamCtx.thread.type,
274
+ mode: runParams.mode,
274
275
  }),
275
- ),
276
+ includeExecutionPlanTools: runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedThreadMode',
277
+ extraMessageCount: runParams.extraMessages?.length ?? 0,
278
+ extraToolCount: runParams.extraTools ? Object.keys(runParams.extraTools).length : 0,
279
+ }),
276
280
  ),
277
281
  )
278
282
 
@@ -13,12 +13,6 @@ import type { UIMessageStreamWriter } from 'ai'
13
13
  import { Clock, Context, Schema, Effect, Layer } from 'effect'
14
14
 
15
15
  import type { CoreThreadProfile } from '../../config/agent-defaults'
16
- import {
17
- getAgentRoster,
18
- getLeadAgentId,
19
- getCoreThreadProfile,
20
- getResolvedAgentFactoryConfig,
21
- } from '../../config/agent-defaults'
22
16
  import { aiLogger } from '../../config/logger'
23
17
  import type { RecordIdRef } from '../../db/record-id'
24
18
  import { recordIdToString } from '../../db/record-id'
@@ -26,8 +20,15 @@ import { TABLES } from '../../db/tables'
26
20
  import type { DatabaseError, ServiceError } from '../../effect/errors'
27
21
  import { ThreadTurnError } from '../../effect/errors'
28
22
  import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
29
- import { enqueueContextCompaction } from '../../queues/context-compaction.queue'
30
- import { enqueueThreadTitleGeneration } from '../../queues/title-generation.queue'
23
+ import {
24
+ AgentConfigServiceTag,
25
+ AgentFactoryServiceTag,
26
+ RuntimeAdaptersServiceTag,
27
+ TurnHooksServiceTag,
28
+ } from '../../effect/services'
29
+ import type { ContextCompactionQueueRuntime } from '../../queues/context-compaction.queue'
30
+ import { LotaQueuesServiceTag } from '../../queues/queues.service'
31
+ import type { TitleGenerationQueueRuntime } from '../../queues/title-generation.queue'
31
32
  import {
32
33
  readRuntimeAgentIdentityOverrides,
33
34
  resolveRuntimeAgentDisplayName,
@@ -42,7 +43,6 @@ import { createExecutionPlanInstructionSectionCache, toExecutionPlanCacheError }
42
43
  import type { HelperModelRuntime } from '../../runtime/helper-model'
43
44
  import { HelperModelTag } from '../../runtime/helper-model'
44
45
  import { runPostTurnSideEffects } from '../../runtime/post-turn-side-effects'
45
- import { getRuntimeAdapters, getTurnHooks } from '../../runtime/runtime-extensions'
46
46
  import { extractMessageText, toOptionalTrimmedString } from '../../runtime/thread-chat-helpers'
47
47
  import {
48
48
  buildPlanTurnInstructionSections,
@@ -93,6 +93,8 @@ interface ThreadTurnPreparationServiceContext {
93
93
  threadMessageService: ReturnType<typeof makeThreadMessageService>
94
94
  threadService: ReturnType<typeof makeThreadService>
95
95
  contextCompactionRuntime: ReturnType<typeof createWiredContextCompactionRuntime>
96
+ contextCompactionQueue: ContextCompactionQueueRuntime
97
+ titleGenerationQueue: TitleGenerationQueueRuntime
96
98
  }
97
99
 
98
100
  type ThreadTurnPreparationRuntimeDeps = ThreadTurnPreparationServiceContext
@@ -365,8 +367,10 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
365
367
  deps: ThreadTurnPreparationRuntimeDeps,
366
368
  params: ThreadRunCoreParams,
367
369
  ) {
368
- const runtimeAdapters = getRuntimeAdapters()
369
- const turnHooks = getTurnHooks()
370
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
371
+ const turnHooks = yield* TurnHooksServiceTag
372
+ const agentConfig = yield* AgentConfigServiceTag
373
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
370
374
  const workspaceProvider = runtimeAdapters.workspaceProvider
371
375
  const workspacePromise =
372
376
  params.kind !== 'approvalContinuation' && workspaceProvider
@@ -488,17 +492,19 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
488
492
  threadRecord.title === THREAD.DEFAULT_TITLE &&
489
493
  messageText.length > 0
490
494
  ) {
491
- void safeEnqueue(() => enqueueThreadTitleGeneration({ threadId: threadIdString, sourceText: messageText }), {
492
- operationName: 'thread-title-generation',
493
- })
495
+ void safeEnqueue(
496
+ () =>
497
+ deps.titleGenerationQueue.enqueueThreadTitleGeneration({ threadId: threadIdString, sourceText: messageText }),
498
+ { operationName: 'thread-title-generation' },
499
+ )
494
500
  }
495
501
 
496
502
  if (thread.type === 'thread' && !thread.threadType) {
497
503
  return yield* new ThreadTurnError({ message: 'Core threads require a thread type.', reason: 'bad-request' })
498
504
  }
499
505
  const coreThreadProfile: CoreThreadProfile | null =
500
- thread.type === 'thread' && thread.threadType ? getCoreThreadProfile(thread.threadType) : null
501
- const defaultLeadAgentId = getLeadAgentId()
506
+ thread.type === 'thread' && thread.threadType ? agentConfig.getCoreThreadProfile(thread.threadType) : null
507
+ const defaultLeadAgentId = agentConfig.leadAgentId
502
508
  const visibleThreadAgentId =
503
509
  params.agentIdOverride ??
504
510
  (thread.type === 'default' ? thread.agentId : (coreThreadProfile?.config.agentId ?? defaultLeadAgentId))
@@ -517,6 +523,8 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
517
523
  workspacePromise,
518
524
  workspaceProvider,
519
525
  turnHooks,
526
+ runtimeAdapters,
527
+ pluginRuntime: agentFactoryConfig.pluginRuntime,
520
528
  }),
521
529
  'Failed to assemble thread turn context.',
522
530
  ).pipe(Effect.withSpan('ThreadTurnPreparation.assembleThreadTurnContext'))
@@ -674,7 +682,11 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
674
682
  serverRunId: string
675
683
  runAbort: ReturnType<typeof createServerRunAbortController>
676
684
  writer?: UIMessageStreamWriter<ChatMessage>
677
- }): Effect.Effect<PreparedThreadTurnResult | void, DatabaseError | ThreadTurnError | ThreadTurnPreparationError> =>
685
+ }): Effect.Effect<
686
+ PreparedThreadTurnResult | void,
687
+ DatabaseError | ThreadTurnError | ThreadTurnPreparationError,
688
+ unknown
689
+ > =>
678
690
  Effect.ensuring(
679
691
  Effect.gen(function* () {
680
692
  const currentRunAbort = runParams.runAbort
@@ -693,8 +705,6 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
693
705
  .pipe(Effect.withSpan('ThreadTurnPreparation.setActiveTurn'))
694
706
  }
695
707
 
696
- const agentFactoryConfig = getResolvedAgentFactoryConfig()
697
-
698
708
  const streamCtx: StreamAgentResponseContext = {
699
709
  turnHooks,
700
710
  thread,
@@ -717,6 +727,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
717
727
  }
718
728
 
719
729
  const { runVisibleAgent } = createThreadTurnVisibleAgentRunner({
730
+ agentConfig,
720
731
  agentFactoryConfig: { buildAgentTools: agentFactoryConfig.buildAgentTools },
721
732
  buildTurnToolParams,
722
733
  threadTurnMessageContext,
@@ -778,7 +789,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
778
789
  ).pipe(Effect.withSpan('ThreadTurnPreparation.executeDirectThread'))
779
790
  } else {
780
791
  const wsMembers = (thread as { members?: string[] }).members ?? []
781
- const members = wsMembers.length > 0 ? wsMembers : [...getAgentRoster()]
792
+ const members = wsMembers.length > 0 ? wsMembers : [...agentConfig.roster]
782
793
  const fallbackAgentId = coreThreadProfile?.config.agentId ?? defaultLeadAgentId
783
794
  yield* failIfRunAborted()
784
795
  writeMultiAgentEvent(runParams.writer, { phase: 'routing', note: 'Routing this turn to the right agent.' })
@@ -791,6 +802,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
791
802
  const triageResult = yield* effectTryPromise(
792
803
  () =>
793
804
  triageThreadMessage({
805
+ agentConfig,
794
806
  threadTitle: thread.title,
795
807
  members,
796
808
  messageText,
@@ -841,6 +853,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
841
853
  const checkResult = yield* effectTryPromise(
842
854
  () =>
843
855
  checkForNextAgent({
856
+ agentConfig,
844
857
  threadTitle: thread.title,
845
858
  members,
846
859
  messageText,
@@ -859,7 +872,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
859
872
  writeMultiAgentEvent(runParams.writer, {
860
873
  phase: 'waiting-for-agent',
861
874
  agentId: checkResult.agentId,
862
- agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
875
+ agentName: resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, checkResult.agentId),
863
876
  note: checkResult.routingContext ?? undefined,
864
877
  })
865
878
 
@@ -890,7 +903,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
890
903
  writeMultiAgentEvent(runParams.writer, {
891
904
  phase: 'agent-message-persisted',
892
905
  agentId: checkResult.agentId,
893
- agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
906
+ agentName: resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, checkResult.agentId),
894
907
  messageId: lastResponse.id,
895
908
  })
896
909
  }
@@ -919,7 +932,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
919
932
  contextSize: CONTEXT_WINDOW_TOKENS,
920
933
  }),
921
934
  enqueueCompaction: () =>
922
- enqueueContextCompaction({
935
+ deps.contextCompactionQueue.enqueueContextCompaction({
923
936
  domain: 'thread',
924
937
  entityId: threadIdString,
925
938
  contextSize: CONTEXT_WINDOW_TOKENS,
@@ -1049,7 +1062,10 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1049
1062
  }),
1050
1063
  )
1051
1064
 
1052
- return runEffect.pipe(Effect.withSpan('ThreadTurnPreparation.executeRun'), Effect.provide(currentContext))
1065
+ return runEffect.pipe(
1066
+ Effect.withSpan('ThreadTurnPreparation.executeRun'),
1067
+ Effect.provide(currentContext),
1068
+ ) as Effect.Effect<PreparedThreadTurnResult, DatabaseError | ThreadTurnError | ThreadTurnPreparationError, never>
1053
1069
  }
1054
1070
 
1055
1071
  return { originalMessages, run }
@@ -1066,6 +1082,8 @@ interface ThreadTurnPreparationDeps {
1066
1082
  threadMessage: ReturnType<typeof makeThreadMessageService>
1067
1083
  thread: ReturnType<typeof makeThreadService>
1068
1084
  helperModelRuntime: HelperModelRuntime
1085
+ contextCompactionQueue: ContextCompactionQueueRuntime
1086
+ titleGenerationQueue: TitleGenerationQueueRuntime
1069
1087
  }
1070
1088
 
1071
1089
  export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps) {
@@ -1084,6 +1102,8 @@ export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps
1084
1102
  now: nowEpochMillis,
1085
1103
  randomId: () => Bun.randomUUIDv7(),
1086
1104
  }),
1105
+ contextCompactionQueue: deps.contextCompactionQueue,
1106
+ titleGenerationQueue: deps.titleGenerationQueue,
1087
1107
  }
1088
1108
  const annotateTurnSpans = <A, E, R>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E, R>) =>
1089
1109
  effect.pipe(
@@ -1132,6 +1152,7 @@ export const ThreadTurnPreparationServiceLive = Layer.effect(
1132
1152
  const planRun = yield* PlanRunServiceTag
1133
1153
  const threadMessage = yield* ThreadMessageServiceTag
1134
1154
  const thread = yield* ThreadServiceTag
1155
+ const queues = yield* LotaQueuesServiceTag
1135
1156
  return makeThreadTurnPreparationService({
1136
1157
  attachment,
1137
1158
  chatRunRegistry,
@@ -1143,6 +1164,8 @@ export const ThreadTurnPreparationServiceLive = Layer.effect(
1143
1164
  threadMessage,
1144
1165
  thread,
1145
1166
  helperModelRuntime: yield* HelperModelTag,
1167
+ contextCompactionQueue: queues.contextCompaction,
1168
+ titleGenerationQueue: queues.titleGeneration,
1146
1169
  })
1147
1170
  }),
1148
1171
  )
@@ -4,18 +4,17 @@ import { convertToModelMessages, stepCountIs } from 'ai'
4
4
  import type { PrepareStepFunction, StopCondition, ToolSet, UIMessageStreamWriter } from 'ai'
5
5
  import { Effect, Ref, Schema, Stream } from 'effect'
6
6
 
7
- import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../../config/agent-defaults'
8
7
  import { aiLogger } from '../../config/logger'
9
8
  import type { RecordIdRef } from '../../db/record-id'
10
9
  import { effectTryMaybeAsync, makeEffectTryPromiseWithMessage } from '../../effect/helpers'
11
- import { runPromise } from '../../effect/runtime'
10
+ import { AgentConfigServiceTag, AgentFactoryServiceTag } from '../../effect/services'
12
11
  import {
13
12
  readRuntimeAgentIdentityOverrides,
14
13
  resolveRuntimeAgentDisplayName,
15
14
  } from '../../runtime/agent-identity-overrides'
16
15
  import { createAgentMessageMetadata } from '../../runtime/agent-stream-helpers'
17
16
  import { mergeInstructionSections } from '../../runtime/instruction-sections'
18
- import type { getTurnHooks } from '../../runtime/runtime-extensions'
17
+ import type { LotaRuntimeTurnHooks } from '../../runtime/runtime-extensions'
19
18
  import {
20
19
  asRecord,
21
20
  collectToolOutputErrors,
@@ -122,7 +121,7 @@ function buildFallbackResponseMessage(
122
121
  }
123
122
 
124
123
  export interface StreamAgentResponseContext {
125
- turnHooks: ReturnType<typeof getTurnHooks>
124
+ turnHooks: LotaRuntimeTurnHooks
126
125
  thread: NormalizedThread
127
126
  threadRef: RecordIdRef
128
127
  orgRef: RecordIdRef
@@ -226,8 +225,9 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
226
225
  (streamParams.skills ?? []).some((skill) => skill.startsWith('cpo-') || skill.startsWith('mentor-')) ||
227
226
  resolvedAgentId === 'cpo' ||
228
227
  resolvedAgentId === 'mentor'
229
- const agentFactoryConfig = getResolvedAgentFactoryConfig()
230
- const config = getAgentRuntimeConfig({
228
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
229
+ const agentConfig = yield* AgentConfigServiceTag
230
+ const config = agentFactoryConfig.getAgentRuntimeConfig({
231
231
  agentId: resolvedAgentId,
232
232
  threadType: ctx.thread.type,
233
233
  mode: streamParams.mode,
@@ -322,7 +322,7 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
322
322
  sendSources: true,
323
323
  messageMetadata: createAgentMessageMetadata({
324
324
  agentId: resolvedAgentId,
325
- agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, resolvedAgentId),
325
+ agentName: resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, resolvedAgentId),
326
326
  }),
327
327
  onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
328
328
  resolveFinishedStream(withMessageCreatedAt(finishedResponseMessage, nowEpochMillis()))
@@ -379,25 +379,20 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
379
379
  return streamedResponse
380
380
  })
381
381
 
382
- export function streamAgentResponse(
383
- ctx: StreamAgentResponseContext,
384
- streamParams: StreamAgentResponseParams,
385
- ): Promise<ChatMessage> {
386
- return runPromise(
387
- streamAgentResponseEffect(ctx, streamParams).pipe(
388
- Effect.annotateSpans(
389
- compactSpanAttributes({
390
- ...buildThreadTurnSpanAttributes({
391
- threadRef: ctx.threadRef,
392
- orgRef: ctx.orgRef,
393
- userRef: ctx.userRef,
394
- agentId: streamParams.agentId,
395
- threadType: ctx.thread.type,
396
- mode: streamParams.mode,
397
- }),
398
- includeExecutionPlanTools: streamParams.includeExecutionPlanTools ?? true,
382
+ export function streamAgentResponse(ctx: StreamAgentResponseContext, streamParams: StreamAgentResponseParams) {
383
+ return streamAgentResponseEffect(ctx, streamParams).pipe(
384
+ Effect.annotateSpans(
385
+ compactSpanAttributes({
386
+ ...buildThreadTurnSpanAttributes({
387
+ threadRef: ctx.threadRef,
388
+ orgRef: ctx.orgRef,
389
+ userRef: ctx.userRef,
390
+ agentId: streamParams.agentId,
391
+ threadType: ctx.thread.type,
392
+ mode: streamParams.mode,
399
393
  }),
400
- ),
394
+ includeExecutionPlanTools: streamParams.includeExecutionPlanTools ?? true,
395
+ }),
401
396
  ),
402
397
  )
403
398
  }
@@ -2,10 +2,11 @@ import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import { createUIMessageStream } from 'ai'
3
3
  import { Context, Schema, Effect, Layer } from 'effect'
4
4
 
5
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
5
6
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
6
7
  import { TABLES } from '../../db/tables'
7
8
  import { BadRequestError } from '../../effect/errors'
8
- import { runPromise } from '../../effect/runtime'
9
+ import { AgentConfigServiceTag } from '../../effect/services'
9
10
  import { hasApprovalRespondedParts, isApprovalContinuationRequest } from '../../runtime/approval-continuation'
10
11
  import { shouldPlanNodeUseVisibleTurn } from '../../runtime/execution-plan-visibility'
11
12
  import { wrapResponseWithKeepalive } from '../../utils/sse-keepalive'
@@ -38,6 +39,7 @@ class ThreadTurnServiceError extends Schema.TaggedErrorClass<ThreadTurnServiceEr
38
39
  }) {}
39
40
 
40
41
  interface ThreadTurnDeps {
42
+ agentConfig: ResolvedAgentConfig
41
43
  planExecutor: ReturnType<typeof makePlanExecutorService>
42
44
  planRun: ReturnType<typeof makePlanRunService>
43
45
  thread: ReturnType<typeof makeThreadService>
@@ -178,7 +180,7 @@ const triggerPlanNodeTurnEffect = Effect.fn('ThreadTurn.triggerPlanNodeTurn')(fu
178
180
  const spec = yield* deps.planRun.getPlanSpecById(run.planSpecId)
179
181
  const nodeSpec = yield* deps.planRun.getNodeSpecByNodeId(spec.id, params.nodeId)
180
182
 
181
- if (!shouldPlanNodeUseVisibleTurn(spec, nodeSpec) || nodeSpec.owner.executorType !== 'agent') {
183
+ if (!shouldPlanNodeUseVisibleTurn(deps.agentConfig, spec, nodeSpec) || nodeSpec.owner.executorType !== 'agent') {
182
184
  return yield* new BadRequestError({
183
185
  message: `Plan node "${params.nodeId}" is not eligible for a visible plan turn.`,
184
186
  })
@@ -187,10 +189,13 @@ const triggerPlanNodeTurnEffect = Effect.fn('ThreadTurn.triggerPlanNodeTurn')(fu
187
189
  let activeRun = run
188
190
  let nodeRun = yield* deps.planRun.getNodeRunByNodeId(run.id, params.nodeId)
189
191
  if (nodeRun.status === 'ready') {
190
- yield* Effect.tryPromise({
191
- try: () => deps.planExecutor.transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId }),
192
- catch: (cause) => new ThreadTurnServiceError({ message: 'Failed to transition plan node to running.', cause }),
193
- })
192
+ yield* deps.planExecutor
193
+ .transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId })
194
+ .pipe(
195
+ Effect.mapError(
196
+ (cause) => new ThreadTurnServiceError({ message: 'Failed to transition plan node to running.', cause }),
197
+ ),
198
+ )
194
199
  activeRun = yield* deps.planRun.getRunById(params.runId)
195
200
  nodeRun = yield* deps.planRun.getNodeRunByNodeId(run.id, params.nodeId)
196
201
  }
@@ -303,44 +308,45 @@ export class ThreadTurnServiceTag extends Context.Service<
303
308
  export const ThreadTurnServiceLive = Layer.effect(
304
309
  ThreadTurnServiceTag,
305
310
  Effect.gen(function* () {
311
+ const agentConfig = yield* AgentConfigServiceTag
306
312
  const planExecutor = yield* PlanExecutorServiceTag
307
313
  const planRun = yield* PlanRunServiceTag
308
314
  const thread = yield* ThreadServiceTag
309
315
  const threadTurnPreparation = yield* ThreadTurnPreparationServiceTag
310
316
  const user = yield* UserServiceTag
311
- return makeThreadTurnService({ planExecutor, planRun, thread, threadTurnPreparation, user })
317
+ return makeThreadTurnService({ agentConfig, planExecutor, planRun, thread, threadTurnPreparation, user })
312
318
  }),
313
319
  )
314
320
 
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
+ export const createThreadApprovalContinuationStream = Effect.fn('ThreadTurn.createApprovalContinuationStream')(
322
+ function* (params: ThreadApprovalContinuationParams) {
323
+ const threadTurnService = yield* ThreadTurnServiceTag
324
+ return yield* threadTurnService.createThreadApprovalContinuationStream(params)
325
+ },
326
+ )
321
327
 
322
- const createThreadNativeToolApprovalStreamWithRuntime = Effect.fn(
323
- 'ThreadTurn.createNativeToolApprovalStreamWithRuntime',
324
- )(function* (params: ThreadApprovalContinuationParams) {
328
+ export const createThreadNativeToolApprovalStream = Effect.fn('ThreadTurn.createNativeToolApprovalStream')(function* (
329
+ params: ThreadApprovalContinuationParams,
330
+ ) {
325
331
  const threadTurnService = yield* ThreadTurnServiceTag
326
332
  return yield* threadTurnService.createThreadNativeToolApprovalStream(params)
327
333
  })
328
334
 
329
- const createThreadTurnStreamWithRuntime = Effect.fn('ThreadTurn.createThreadTurnStreamWithRuntime')(function* (
335
+ export const createThreadTurnStream = Effect.fn('ThreadTurn.createThreadTurnStream')(function* (
330
336
  params: ThreadTurnParams,
331
337
  ) {
332
338
  const threadTurnService = yield* ThreadTurnServiceTag
333
339
  return yield* threadTurnService.createThreadTurnStream(params)
334
340
  })
335
341
 
336
- const runThreadTurnInBackgroundWithRuntime = Effect.fn('ThreadTurn.runThreadTurnInBackgroundWithRuntime')(function* (
342
+ export const runThreadTurnInBackground = Effect.fn('ThreadTurn.runThreadTurnInBackground')(function* (
337
343
  params: ThreadTurnParams,
338
344
  ) {
339
345
  const threadTurnService = yield* ThreadTurnServiceTag
340
346
  return yield* threadTurnService.runThreadTurnInBackground(params)
341
347
  })
342
348
 
343
- const triggerPlanNodeTurnWithRuntime = Effect.fn('ThreadTurn.triggerPlanNodeTurnWithRuntime')(function* (params: {
349
+ export const triggerPlanNodeTurn = Effect.fn('ThreadTurn.triggerPlanNodeTurn')(function* (params: {
344
350
  runId: string
345
351
  nodeId: string
346
352
  abortSignal?: AbortSignal
@@ -349,28 +355,3 @@ const triggerPlanNodeTurnWithRuntime = Effect.fn('ThreadTurn.triggerPlanNodeTurn
349
355
  const threadTurnService = yield* ThreadTurnServiceTag
350
356
  return yield* threadTurnService.triggerPlanNodeTurn(params)
351
357
  })
352
-
353
- export function createThreadApprovalContinuationStream(params: ThreadApprovalContinuationParams) {
354
- return runPromise(createThreadApprovalContinuationStreamWithRuntime(params))
355
- }
356
-
357
- export function createThreadNativeToolApprovalStream(params: ThreadApprovalContinuationParams) {
358
- return runPromise(createThreadNativeToolApprovalStreamWithRuntime(params))
359
- }
360
-
361
- export function createThreadTurnStream(params: ThreadTurnParams) {
362
- return runPromise(createThreadTurnStreamWithRuntime(params))
363
- }
364
-
365
- export function runThreadTurnInBackground(params: ThreadTurnParams): Promise<PreparedThreadTurnResult> {
366
- return runPromise(runThreadTurnInBackgroundWithRuntime(params))
367
- }
368
-
369
- export function triggerPlanNodeTurn(params: {
370
- runId: string
371
- nodeId: string
372
- abortSignal?: AbortSignal
373
- streamId?: string
374
- }): Promise<PreparedThreadTurnResult> {
375
- return runPromise(triggerPlanNodeTurnWithRuntime(params))
376
- }