@lota-sdk/core 0.4.9 → 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 (182) 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 +164 -82
  4. package/src/ai-gateway/index.ts +16 -1
  5. package/src/config/agent-defaults.ts +4 -107
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/config/background-processing.ts +1 -1
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +22 -25
  10. package/src/config/thread-defaults.ts +1 -10
  11. package/src/create-runtime.ts +145 -670
  12. package/src/db/base.service.ts +30 -38
  13. package/src/db/memory-query-builder.ts +2 -1
  14. package/src/db/memory-store.ts +29 -20
  15. package/src/db/memory.ts +188 -195
  16. package/src/db/service-normalization.ts +97 -64
  17. package/src/db/service.ts +496 -384
  18. package/src/db/startup.ts +30 -19
  19. package/src/effect/helpers.ts +30 -5
  20. package/src/effect/index.ts +7 -7
  21. package/src/effect/layers.ts +75 -72
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -71
  24. package/src/index.ts +13 -12
  25. package/src/queues/autonomous-job.queue.ts +177 -143
  26. package/src/queues/context-compaction.queue.ts +41 -39
  27. package/src/queues/delayed-node-promotion.queue.ts +61 -42
  28. package/src/queues/document-processor.queue.ts +5 -3
  29. package/src/queues/index.ts +1 -0
  30. package/src/queues/memory-consolidation.queue.ts +79 -53
  31. package/src/queues/organization-learning.queue.ts +70 -33
  32. package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
  33. package/src/queues/plan-scheduler.queue.ts +101 -97
  34. package/src/queues/post-chat-memory.queue.ts +56 -46
  35. package/src/queues/queue-factory.ts +146 -69
  36. package/src/queues/queues.service.ts +61 -0
  37. package/src/queues/title-generation.queue.ts +44 -44
  38. package/src/redis/connection.ts +181 -164
  39. package/src/redis/org-memory-lock.ts +24 -9
  40. package/src/redis/redis-lease-lock.ts +8 -1
  41. package/src/redis/stream-context.ts +17 -9
  42. package/src/runtime/agent-identity-overrides.ts +7 -3
  43. package/src/runtime/agent-runtime-policy.ts +10 -5
  44. package/src/runtime/agent-stream-helpers.ts +24 -15
  45. package/src/runtime/chat-run-orchestration.ts +1 -1
  46. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  47. package/src/runtime/context-compaction/context-compaction.ts +131 -85
  48. package/src/runtime/domain-layer.ts +203 -0
  49. package/src/runtime/execution-plan-visibility.ts +5 -2
  50. package/src/runtime/graph-designer.ts +0 -14
  51. package/src/runtime/helper-model.ts +8 -4
  52. package/src/runtime/index.ts +1 -1
  53. package/src/runtime/indexed-repositories-policy.ts +2 -6
  54. package/src/runtime/memory/memory-block.ts +19 -9
  55. package/src/runtime/memory/memory-pipeline.ts +53 -66
  56. package/src/runtime/memory/memory-scope.ts +33 -29
  57. package/src/runtime/plugin-resolution.ts +58 -62
  58. package/src/runtime/post-turn-side-effects.ts +139 -161
  59. package/src/runtime/retrieval-adapters.ts +4 -4
  60. package/src/runtime/runtime-config.ts +3 -9
  61. package/src/runtime/runtime-extensions.ts +0 -43
  62. package/src/runtime/runtime-lifecycle.ts +124 -0
  63. package/src/runtime/runtime-services.ts +455 -0
  64. package/src/runtime/runtime-worker-registry.ts +113 -30
  65. package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
  66. package/src/runtime/social-chat/social-chat-history.ts +24 -13
  67. package/src/runtime/social-chat/social-chat.ts +420 -369
  68. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
  69. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  70. package/src/runtime/thread-chat-helpers.ts +18 -9
  71. package/src/runtime/thread-turn-context.ts +28 -74
  72. package/src/runtime/turn-lifecycle.ts +6 -14
  73. package/src/services/agent-activity.service.ts +169 -176
  74. package/src/services/agent-executor.service.ts +207 -196
  75. package/src/services/artifact.service.ts +10 -5
  76. package/src/services/attachment.service.ts +16 -48
  77. package/src/services/autonomous-job.service.ts +81 -87
  78. package/src/services/background-work.service.ts +54 -0
  79. package/src/services/chat-run-registry.service.ts +3 -1
  80. package/src/services/context-compaction.service.ts +8 -10
  81. package/src/services/document-chunk.service.ts +8 -17
  82. package/src/services/execution-plan/execution-plan-graph.ts +122 -109
  83. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  84. package/src/services/execution-plan/execution-plan.service.ts +68 -51
  85. package/src/services/feedback-loop.service.ts +1 -1
  86. package/src/services/global-orchestrator.service.ts +49 -15
  87. package/src/services/graph-full-routing.ts +49 -37
  88. package/src/services/index.ts +1 -0
  89. package/src/services/institutional-memory.service.ts +8 -17
  90. package/src/services/learned-skill.service.ts +38 -35
  91. package/src/services/memory/memory-conversation.ts +10 -5
  92. package/src/services/memory/memory-errors.ts +27 -0
  93. package/src/services/memory/memory-org-memory.ts +14 -3
  94. package/src/services/memory/memory-preseeded.ts +10 -4
  95. package/src/services/memory/memory-utils.ts +2 -1
  96. package/src/services/memory/memory.service.ts +37 -52
  97. package/src/services/memory/rerank.service.ts +3 -11
  98. package/src/services/monitoring-window.service.ts +1 -1
  99. package/src/services/mutating-approval.service.ts +1 -1
  100. package/src/services/node-workspace.service.ts +2 -2
  101. package/src/services/notification.service.ts +16 -4
  102. package/src/services/organization-member.service.ts +1 -1
  103. package/src/services/organization.service.ts +34 -51
  104. package/src/services/ownership-dispatcher.service.ts +148 -95
  105. package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
  106. package/src/services/plan/plan-agent-query.service.ts +13 -9
  107. package/src/services/plan/plan-approval.service.ts +52 -48
  108. package/src/services/plan/plan-artifact.service.ts +2 -2
  109. package/src/services/plan/plan-builder.service.ts +2 -2
  110. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  111. package/src/services/plan/plan-compiler.service.ts +1 -1
  112. package/src/services/plan/plan-completion-side-effects.ts +99 -113
  113. package/src/services/plan/plan-coordination.service.ts +1 -1
  114. package/src/services/plan/plan-cycle.service.ts +171 -202
  115. package/src/services/plan/plan-deadline.service.ts +304 -307
  116. package/src/services/plan/plan-event-delivery.service.ts +84 -72
  117. package/src/services/plan/plan-executor-context.ts +2 -0
  118. package/src/services/plan/plan-executor-graph.ts +375 -353
  119. package/src/services/plan/plan-executor-helpers.ts +60 -75
  120. package/src/services/plan/plan-executor.service.ts +494 -489
  121. package/src/services/plan/plan-run.service.ts +12 -19
  122. package/src/services/plan/plan-scheduler.service.ts +89 -82
  123. package/src/services/plan/plan-template.service.ts +1 -1
  124. package/src/services/plan/plan-transaction-events.ts +8 -5
  125. package/src/services/plan/plan-validator.service.ts +1 -1
  126. package/src/services/plan/plan-workspace.service.ts +17 -11
  127. package/src/services/plugin-executor.service.ts +26 -21
  128. package/src/services/quality-metrics.service.ts +1 -1
  129. package/src/services/queue-job.service.ts +8 -17
  130. package/src/services/recent-activity-title.service.ts +22 -10
  131. package/src/services/recent-activity.service.ts +1 -1
  132. package/src/services/skill-resolver.service.ts +1 -1
  133. package/src/services/social-chat-history.service.ts +37 -20
  134. package/src/services/system-executor.service.ts +25 -20
  135. package/src/services/thread/thread-bootstrap.ts +37 -19
  136. package/src/services/thread/thread-listing.ts +2 -1
  137. package/src/services/thread/thread-memory-block.ts +18 -5
  138. package/src/services/thread/thread-message.service.ts +30 -13
  139. package/src/services/thread/thread-title.service.ts +1 -1
  140. package/src/services/thread/thread-turn-execution.ts +87 -83
  141. package/src/services/thread/thread-turn-preparation.service.ts +65 -40
  142. package/src/services/thread/thread-turn-streaming.ts +32 -36
  143. package/src/services/thread/thread-turn.ts +43 -29
  144. package/src/services/thread/thread.service.ts +32 -8
  145. package/src/services/user.service.ts +1 -1
  146. package/src/services/write-intent-validator.service.ts +1 -1
  147. package/src/storage/attachment-storage.service.ts +7 -4
  148. package/src/storage/generated-document-storage.service.ts +1 -1
  149. package/src/system-agents/context-compaction.agent.ts +1 -1
  150. package/src/system-agents/helper-agent-options.ts +1 -1
  151. package/src/system-agents/memory-reranker.agent.ts +1 -1
  152. package/src/system-agents/memory.agent.ts +1 -1
  153. package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
  154. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  155. package/src/system-agents/skill-extractor.agent.ts +1 -1
  156. package/src/system-agents/skill-manager.agent.ts +1 -1
  157. package/src/system-agents/thread-router.agent.ts +23 -20
  158. package/src/system-agents/title-generator.agent.ts +1 -1
  159. package/src/tools/execution-plan.tool.ts +36 -20
  160. package/src/tools/fetch-webpage.tool.ts +30 -22
  161. package/src/tools/firecrawl-client.ts +1 -6
  162. package/src/tools/plan-approval.tool.ts +9 -1
  163. package/src/tools/remember-memory.tool.ts +3 -6
  164. package/src/tools/research-topic.tool.ts +12 -3
  165. package/src/tools/search-web.tool.ts +26 -18
  166. package/src/tools/search.tool.ts +4 -5
  167. package/src/tools/team-think.tool.ts +139 -121
  168. package/src/utils/async.ts +15 -6
  169. package/src/utils/errors.ts +27 -15
  170. package/src/workers/bootstrap.ts +34 -58
  171. package/src/workers/memory-consolidation.worker.ts +4 -1
  172. package/src/workers/organization-learning.worker.ts +16 -3
  173. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  174. package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
  175. package/src/workers/skill-extraction.runner.ts +13 -15
  176. package/src/workers/worker-utils.ts +14 -8
  177. package/src/config/search.ts +0 -3
  178. package/src/effect/awaitable-effect.ts +0 -87
  179. package/src/effect/runtime-ref.ts +0 -25
  180. package/src/effect/runtime.ts +0 -31
  181. package/src/redis/runtime-connection.ts +0 -10
  182. package/src/runtime/agent-types.ts +0 -1
@@ -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,19 +131,18 @@ 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
  },
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
 
@@ -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'))
@@ -570,20 +578,22 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
570
578
  return Effect.succeed(preSeededMemoriesByAgent.get(agentId))
571
579
  }
572
580
 
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
- )
581
+ return memoryService
582
+ .getTopMemories({ orgId: orgIdString, agentName: agentId, limit: PRESEEDED_MEMORY_LOOKUP_LIMIT })
583
+ .pipe(
584
+ Effect.tap((preSeededMemories) =>
585
+ Effect.sync(() => {
586
+ preSeededMemoriesByAgent.set(agentId, preSeededMemories)
587
+ }),
588
+ ),
589
+ Effect.mapError(
590
+ (error) =>
591
+ new ThreadTurnStreamingError({
592
+ message: `Failed to load pre-seeded memories for ${agentId}.`,
593
+ cause: error,
594
+ }),
595
+ ),
596
+ )
587
597
  }
588
598
 
589
599
  const learnedSkillsByAgent = new Map<string, string | undefined>()
@@ -672,7 +682,11 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
672
682
  serverRunId: string
673
683
  runAbort: ReturnType<typeof createServerRunAbortController>
674
684
  writer?: UIMessageStreamWriter<ChatMessage>
675
- }): Effect.Effect<PreparedThreadTurnResult | void, DatabaseError | ThreadTurnError | ThreadTurnPreparationError> =>
685
+ }): Effect.Effect<
686
+ PreparedThreadTurnResult | void,
687
+ DatabaseError | ThreadTurnError | ThreadTurnPreparationError,
688
+ unknown
689
+ > =>
676
690
  Effect.ensuring(
677
691
  Effect.gen(function* () {
678
692
  const currentRunAbort = runParams.runAbort
@@ -691,8 +705,6 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
691
705
  .pipe(Effect.withSpan('ThreadTurnPreparation.setActiveTurn'))
692
706
  }
693
707
 
694
- const agentFactoryConfig = getResolvedAgentFactoryConfig()
695
-
696
708
  const streamCtx: StreamAgentResponseContext = {
697
709
  turnHooks,
698
710
  thread,
@@ -715,6 +727,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
715
727
  }
716
728
 
717
729
  const { runVisibleAgent } = createThreadTurnVisibleAgentRunner({
730
+ agentConfig,
718
731
  agentFactoryConfig: { buildAgentTools: agentFactoryConfig.buildAgentTools },
719
732
  buildTurnToolParams,
720
733
  threadTurnMessageContext,
@@ -776,7 +789,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
776
789
  ).pipe(Effect.withSpan('ThreadTurnPreparation.executeDirectThread'))
777
790
  } else {
778
791
  const wsMembers = (thread as { members?: string[] }).members ?? []
779
- const members = wsMembers.length > 0 ? wsMembers : [...getAgentRoster()]
792
+ const members = wsMembers.length > 0 ? wsMembers : [...agentConfig.roster]
780
793
  const fallbackAgentId = coreThreadProfile?.config.agentId ?? defaultLeadAgentId
781
794
  yield* failIfRunAborted()
782
795
  writeMultiAgentEvent(runParams.writer, { phase: 'routing', note: 'Routing this turn to the right agent.' })
@@ -789,6 +802,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
789
802
  const triageResult = yield* effectTryPromise(
790
803
  () =>
791
804
  triageThreadMessage({
805
+ agentConfig,
792
806
  threadTitle: thread.title,
793
807
  members,
794
808
  messageText,
@@ -839,6 +853,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
839
853
  const checkResult = yield* effectTryPromise(
840
854
  () =>
841
855
  checkForNextAgent({
856
+ agentConfig,
842
857
  threadTitle: thread.title,
843
858
  members,
844
859
  messageText,
@@ -857,7 +872,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
857
872
  writeMultiAgentEvent(runParams.writer, {
858
873
  phase: 'waiting-for-agent',
859
874
  agentId: checkResult.agentId,
860
- agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
875
+ agentName: resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, checkResult.agentId),
861
876
  note: checkResult.routingContext ?? undefined,
862
877
  })
863
878
 
@@ -888,7 +903,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
888
903
  writeMultiAgentEvent(runParams.writer, {
889
904
  phase: 'agent-message-persisted',
890
905
  agentId: checkResult.agentId,
891
- agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
906
+ agentName: resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, checkResult.agentId),
892
907
  messageId: lastResponse.id,
893
908
  })
894
909
  }
@@ -917,7 +932,7 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
917
932
  contextSize: CONTEXT_WINDOW_TOKENS,
918
933
  }),
919
934
  enqueueCompaction: () =>
920
- enqueueContextCompaction({
935
+ deps.contextCompactionQueue.enqueueContextCompaction({
921
936
  domain: 'thread',
922
937
  entityId: threadIdString,
923
938
  contextSize: CONTEXT_WINDOW_TOKENS,
@@ -1047,7 +1062,10 @@ const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThrea
1047
1062
  }),
1048
1063
  )
1049
1064
 
1050
- 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>
1051
1069
  }
1052
1070
 
1053
1071
  return { originalMessages, run }
@@ -1064,6 +1082,8 @@ interface ThreadTurnPreparationDeps {
1064
1082
  threadMessage: ReturnType<typeof makeThreadMessageService>
1065
1083
  thread: ReturnType<typeof makeThreadService>
1066
1084
  helperModelRuntime: HelperModelRuntime
1085
+ contextCompactionQueue: ContextCompactionQueueRuntime
1086
+ titleGenerationQueue: TitleGenerationQueueRuntime
1067
1087
  }
1068
1088
 
1069
1089
  export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps) {
@@ -1082,8 +1102,10 @@ export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps
1082
1102
  now: nowEpochMillis,
1083
1103
  randomId: () => Bun.randomUUIDv7(),
1084
1104
  }),
1105
+ contextCompactionQueue: deps.contextCompactionQueue,
1106
+ titleGenerationQueue: deps.titleGenerationQueue,
1085
1107
  }
1086
- const annotateTurnSpans = <A, E>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E>) =>
1108
+ const annotateTurnSpans = <A, E, R>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E, R>) =>
1087
1109
  effect.pipe(
1088
1110
  Effect.annotateSpans(
1089
1111
  compactSpanAttributes({
@@ -1116,7 +1138,7 @@ export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps
1116
1138
  export class ThreadTurnPreparationServiceTag extends Context.Service<
1117
1139
  ThreadTurnPreparationServiceTag,
1118
1140
  ReturnType<typeof makeThreadTurnPreparationService>
1119
- >()('ThreadTurnPreparationService') {}
1141
+ >()('@lota-sdk/core/ThreadTurnPreparationService') {}
1120
1142
 
1121
1143
  export const ThreadTurnPreparationServiceLive = Layer.effect(
1122
1144
  ThreadTurnPreparationServiceTag,
@@ -1130,6 +1152,7 @@ export const ThreadTurnPreparationServiceLive = Layer.effect(
1130
1152
  const planRun = yield* PlanRunServiceTag
1131
1153
  const threadMessage = yield* ThreadMessageServiceTag
1132
1154
  const thread = yield* ThreadServiceTag
1155
+ const queues = yield* LotaQueuesServiceTag
1133
1156
  return makeThreadTurnPreparationService({
1134
1157
  attachment,
1135
1158
  chatRunRegistry,
@@ -1141,6 +1164,8 @@ export const ThreadTurnPreparationServiceLive = Layer.effect(
1141
1164
  threadMessage,
1142
1165
  thread,
1143
1166
  helperModelRuntime: yield* HelperModelTag,
1167
+ contextCompactionQueue: queues.contextCompaction,
1168
+ titleGenerationQueue: queues.titleGeneration,
1144
1169
  })
1145
1170
  }),
1146
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
- import { effectTryMaybeAsync, effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
11
- import { runPromise } from '../../effect/runtime'
9
+ import { effectTryMaybeAsync, makeEffectTryPromiseWithMessage } from '../../effect/helpers'
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,
@@ -44,12 +43,9 @@ export class ThreadTurnStreamingError extends Schema.TaggedErrorClass<ThreadTurn
44
43
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
45
44
  ) {}
46
45
 
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
- }
46
+ const effectTryPromise = makeEffectTryPromiseWithMessage(
47
+ (message, cause) => new ThreadTurnStreamingError({ message, cause }),
48
+ )
53
49
 
54
50
  function effectFromMaybeEffect<A>(
55
51
  evaluate: () => A | PromiseLike<A> | Effect.Effect<A, ThreadTurnStreamingError>,
@@ -93,7 +89,9 @@ function isTextTokenChunkType(chunkType: string | undefined): boolean {
93
89
  return chunkType === 'text-delta'
94
90
  }
95
91
 
96
- function buildFallbackResponseMessage(result: ToolLoopGenerateResult): ChatMessage {
92
+ function buildFallbackResponseMessage(
93
+ result: ToolLoopGenerateResult,
94
+ ): Effect.Effect<ChatMessage, ThreadTurnStreamingError> {
97
95
  const parts: ChatMessage['parts'] = []
98
96
 
99
97
  for (const step of result.steps ?? []) {
@@ -114,14 +112,16 @@ function buildFallbackResponseMessage(result: ToolLoopGenerateResult): ChatMessa
114
112
  }
115
113
 
116
114
  if (parts.length === 0) {
117
- throw new Error('Agent generate fallback did not produce any response parts.')
115
+ return Effect.fail(
116
+ new ThreadTurnStreamingError({ message: 'Agent generate fallback did not produce any response parts.' }),
117
+ )
118
118
  }
119
119
 
120
- return { id: Bun.randomUUIDv7(), role: 'assistant', parts }
120
+ return Effect.succeed({ id: Bun.randomUUIDv7(), role: 'assistant', parts })
121
121
  }
122
122
 
123
123
  export interface StreamAgentResponseContext {
124
- turnHooks: ReturnType<typeof getTurnHooks>
124
+ turnHooks: LotaRuntimeTurnHooks
125
125
  thread: NormalizedThread
126
126
  threadRef: RecordIdRef
127
127
  orgRef: RecordIdRef
@@ -225,8 +225,9 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
225
225
  (streamParams.skills ?? []).some((skill) => skill.startsWith('cpo-') || skill.startsWith('mentor-')) ||
226
226
  resolvedAgentId === 'cpo' ||
227
227
  resolvedAgentId === 'mentor'
228
- const agentFactoryConfig = getResolvedAgentFactoryConfig()
229
- const config = getAgentRuntimeConfig({
228
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
229
+ const agentConfig = yield* AgentConfigServiceTag
230
+ const config = agentFactoryConfig.getAgentRuntimeConfig({
230
231
  agentId: resolvedAgentId,
231
232
  threadType: ctx.thread.type,
232
233
  mode: streamParams.mode,
@@ -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(
@@ -321,7 +322,7 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
321
322
  sendSources: true,
322
323
  messageMetadata: createAgentMessageMetadata({
323
324
  agentId: resolvedAgentId,
324
- agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, resolvedAgentId),
325
+ agentName: resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, resolvedAgentId),
325
326
  }),
326
327
  onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
327
328
  resolveFinishedStream(withMessageCreatedAt(finishedResponseMessage, nowEpochMillis()))
@@ -378,25 +379,20 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
378
379
  return streamedResponse
379
380
  })
380
381
 
381
- export function streamAgentResponse(
382
- ctx: StreamAgentResponseContext,
383
- streamParams: StreamAgentResponseParams,
384
- ): Promise<ChatMessage> {
385
- return runPromise(
386
- streamAgentResponseEffect(ctx, streamParams).pipe(
387
- Effect.annotateSpans(
388
- compactSpanAttributes({
389
- ...buildThreadTurnSpanAttributes({
390
- threadRef: ctx.threadRef,
391
- orgRef: ctx.orgRef,
392
- userRef: ctx.userRef,
393
- agentId: streamParams.agentId,
394
- threadType: ctx.thread.type,
395
- mode: streamParams.mode,
396
- }),
397
- 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,
398
393
  }),
399
- ),
394
+ includeExecutionPlanTools: streamParams.includeExecutionPlanTools ?? true,
395
+ }),
400
396
  ),
401
397
  )
402
398
  }