@lota-sdk/core 0.4.10 → 0.4.12

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 (110) hide show
  1. package/package.json +3 -3
  2. package/src/ai-gateway/ai-gateway.ts +214 -98
  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/model-constants.ts +1 -0
  7. package/src/config/thread-defaults.ts +1 -18
  8. package/src/create-runtime.ts +90 -28
  9. package/src/db/base.service.ts +30 -38
  10. package/src/db/service.ts +489 -545
  11. package/src/effect/index.ts +0 -2
  12. package/src/effect/layers.ts +6 -13
  13. package/src/embeddings/provider.ts +2 -7
  14. package/src/index.ts +4 -5
  15. package/src/queues/autonomous-job.queue.ts +159 -113
  16. package/src/queues/context-compaction.queue.ts +39 -25
  17. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  18. package/src/queues/document-processor.queue.ts +5 -3
  19. package/src/queues/index.ts +1 -0
  20. package/src/queues/memory-consolidation.queue.ts +79 -53
  21. package/src/queues/organization-learning.queue.ts +63 -39
  22. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  23. package/src/queues/plan-scheduler.queue.ts +100 -84
  24. package/src/queues/post-chat-memory.queue.ts +55 -33
  25. package/src/queues/queue-factory.ts +40 -41
  26. package/src/queues/queues.service.ts +61 -0
  27. package/src/queues/title-generation.queue.ts +42 -31
  28. package/src/redis/org-memory-lock.ts +24 -9
  29. package/src/redis/redis-lease-lock.ts +8 -1
  30. package/src/runtime/agent-identity-overrides.ts +7 -3
  31. package/src/runtime/agent-runtime-policy.ts +9 -4
  32. package/src/runtime/agent-stream-helpers.ts +9 -4
  33. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  34. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  35. package/src/runtime/domain-layer.ts +15 -4
  36. package/src/runtime/execution-plan-visibility.ts +5 -2
  37. package/src/runtime/graph-designer.ts +0 -22
  38. package/src/runtime/index.ts +2 -0
  39. package/src/runtime/indexed-repositories-policy.ts +2 -6
  40. package/src/runtime/live-turn-trace.ts +344 -0
  41. package/src/runtime/plugin-resolution.ts +29 -12
  42. package/src/runtime/post-turn-side-effects.ts +139 -141
  43. package/src/runtime/runtime-config.ts +0 -6
  44. package/src/runtime/runtime-extensions.ts +0 -54
  45. package/src/runtime/runtime-lifecycle.ts +4 -4
  46. package/src/runtime/runtime-services.ts +125 -53
  47. package/src/runtime/runtime-worker-registry.ts +113 -30
  48. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  49. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  50. package/src/runtime/social-chat/social-chat.ts +35 -20
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  52. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  53. package/src/runtime/thread-chat-helpers.ts +18 -9
  54. package/src/runtime/thread-turn-context.ts +7 -47
  55. package/src/runtime/turn-lifecycle.ts +6 -14
  56. package/src/services/agent-activity.service.ts +168 -175
  57. package/src/services/agent-executor.service.ts +35 -16
  58. package/src/services/attachment.service.ts +4 -70
  59. package/src/services/autonomous-job.service.ts +53 -61
  60. package/src/services/context-compaction.service.ts +7 -9
  61. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  62. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  63. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  64. package/src/services/global-orchestrator.service.ts +18 -7
  65. package/src/services/graph-full-routing.ts +7 -6
  66. package/src/services/memory/memory-conversation.ts +10 -5
  67. package/src/services/memory/memory.service.ts +11 -8
  68. package/src/services/ownership-dispatcher.service.ts +16 -5
  69. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  70. package/src/services/plan/plan-agent-query.service.ts +12 -8
  71. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  72. package/src/services/plan/plan-cycle.service.ts +7 -45
  73. package/src/services/plan/plan-deadline.service.ts +28 -17
  74. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  75. package/src/services/plan/plan-executor-context.ts +2 -0
  76. package/src/services/plan/plan-executor-graph.ts +366 -391
  77. package/src/services/plan/plan-executor.service.ts +13 -91
  78. package/src/services/plan/plan-scheduler.service.ts +62 -49
  79. package/src/services/plan/plan-transaction-events.ts +1 -1
  80. package/src/services/recent-activity-title.service.ts +6 -2
  81. package/src/services/thread/thread-bootstrap.ts +11 -9
  82. package/src/services/thread/thread-message.service.ts +6 -5
  83. package/src/services/thread/thread-turn-execution.ts +86 -82
  84. package/src/services/thread/thread-turn-preparation.service.ts +92 -45
  85. package/src/services/thread/thread-turn-streaming.ts +60 -28
  86. package/src/services/thread/thread-turn.ts +212 -46
  87. package/src/services/thread/thread.service.ts +21 -6
  88. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  89. package/src/system-agents/thread-router.agent.ts +23 -20
  90. package/src/tools/execution-plan.tool.ts +8 -3
  91. package/src/tools/fetch-webpage.tool.ts +10 -9
  92. package/src/tools/firecrawl-client.ts +0 -15
  93. package/src/tools/remember-memory.tool.ts +3 -6
  94. package/src/tools/research-topic.tool.ts +12 -3
  95. package/src/tools/search-web.tool.ts +10 -9
  96. package/src/tools/search.tool.ts +4 -5
  97. package/src/tools/team-think.tool.ts +139 -121
  98. package/src/workers/bootstrap.ts +9 -10
  99. package/src/workers/memory-consolidation.worker.ts +4 -1
  100. package/src/workers/organization-learning.worker.ts +15 -2
  101. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  102. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  103. package/src/workers/skill-extraction.runner.ts +13 -15
  104. package/src/workers/worker-utils.ts +6 -18
  105. package/src/effect/awaitable-effect.ts +0 -96
  106. package/src/effect/runtime-ref.ts +0 -25
  107. package/src/effect/runtime.ts +0 -46
  108. package/src/redis/runtime-connection.ts +0 -20
  109. package/src/runtime/runtime-accessors.ts +0 -92
  110. package/src/runtime/runtime-token.ts +0 -47
@@ -3,16 +3,20 @@ import { stepCountIs } from 'ai'
3
3
  import type { ToolSet } from 'ai'
4
4
  import { Schema, Effect } from 'effect'
5
5
 
6
- import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../config/agent-defaults'
7
6
  import { aiLogger } from '../config/logger'
8
7
  import type { RecordIdRef } from '../db/record-id'
9
8
  import { recordIdToString } from '../db/record-id'
10
9
  import { TABLES } from '../db/tables'
11
10
  import { effectTryMaybeAsync as effectTryMaybeAsyncShared } from '../effect/helpers'
11
+ import {
12
+ AgentConfigServiceTag,
13
+ AgentFactoryServiceTag,
14
+ RuntimeAdaptersServiceTag,
15
+ TurnHooksServiceTag,
16
+ } from '../effect/services'
12
17
  import { readRuntimeAgentIdentityOverrides } from '../runtime/agent-identity-overrides'
13
18
  import { mergeInstructionSections } from '../runtime/instruction-sections'
14
- import { getRuntimeAdapters, getTurnHooks } from '../runtime/runtime-extensions'
15
- import type { LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
19
+ import type { LotaRuntimeAdapters, LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
16
20
  import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation/team-consultation-orchestrator'
17
21
  import type {
18
22
  DefaultRepoSections,
@@ -24,8 +28,9 @@ import type { ReadableUploadMetadata } from '../services/attachment.service'
24
28
 
25
29
  function buildTeamThinkAgentToolsEffect(
26
30
  params: LotaRuntimeTeamThinkToolsParams,
31
+ runtimeAdapters: LotaRuntimeAdapters,
27
32
  ): Effect.Effect<{ tools: ToolSet }, TeamThinkRuntimeError> {
28
- const builder = getRuntimeAdapters().buildTeamThinkAgentTools
33
+ const builder = runtimeAdapters.buildTeamThinkAgentTools
29
34
  if (!builder) {
30
35
  return Effect.succeed({ tools: {} })
31
36
  }
@@ -72,124 +77,137 @@ export function createTeamThinkTool(params: {
72
77
  toolProviders?: ToolSet
73
78
  abortSignal: AbortSignal
74
79
  }) {
75
- const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(
76
- (params.context as Record<string, unknown> | null | undefined) ?? null,
77
- )
78
- const participantRunner: TeamConsultationParticipantRunner = {
79
- buildParticipantAgent(agentId, runParams) {
80
- return Effect.runPromise(
81
- Effect.gen(function* () {
82
- const currentContext = yield* Effect.context()
83
- const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
84
- const dynamicInstructionSections = yield* effectTryMaybeAsync(
85
- () => params.getAdditionalInstructionSections?.(),
86
- 'Failed to load dynamic team-think instruction sections.',
87
- )
88
- const agentResolution = asRecord(
89
- yield* effectTryMaybeAsync(
90
- () =>
91
- getTurnHooks().resolveAgent?.({
92
- agentId,
93
- mode: 'fixedThreadMode',
94
- thread: null,
95
- threadRef: params.threadId,
96
- orgRef: params.orgId,
97
- userRef: params.userId,
98
- onboardingActive: false,
99
- linearInstalled: false,
100
- githubInstalled: params.githubInstalled,
101
- additionalInstructionSections: mergeInstructionSections(
102
- dynamicInstructionSections,
103
- params.additionalInstructionSections,
104
- ),
105
- context: (params.context as Record<string, unknown> | null | undefined) ?? null,
106
- }),
107
- 'Failed to resolve team-think participant agent.',
108
- ),
109
- )
110
- const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
111
- const config = getAgentRuntimeConfig({
112
- agentId: resolvedAgentId,
113
- threadType: 'group' as const,
114
- mode: 'fixedThreadMode',
115
- onboardingActive: false,
116
- linearInstalled: false,
117
- systemWorkspaceDetails: runParams.systemWorkspaceDetails,
118
- preSeededMemoriesSection: runParams.preSeededMemoriesSection,
119
- retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
120
- additionalInstructionSections: mergeInstructionSections(
121
- dynamicInstructionSections,
122
- params.additionalInstructionSections,
123
- readInstructionSections(agentResolution?.additionalInstructionSections),
124
- ),
125
- responseGuardSection: buildTeamConsultationResponseGuard({
80
+ return Effect.gen(function* () {
81
+ const turnHooks = yield* TurnHooksServiceTag
82
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
83
+ const agentConfig = yield* AgentConfigServiceTag
84
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
85
+ const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(
86
+ (params.context as Record<string, unknown> | null | undefined) ?? null,
87
+ )
88
+ const participantRunner: TeamConsultationParticipantRunner = {
89
+ buildParticipantAgent(agentId, runParams) {
90
+ return Effect.runPromise(
91
+ Effect.gen(function* () {
92
+ const currentContext = yield* Effect.context()
93
+ const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
94
+ const dynamicInstructionSections = yield* effectTryMaybeAsync(
95
+ () => params.getAdditionalInstructionSections?.(),
96
+ 'Failed to load dynamic team-think instruction sections.',
97
+ )
98
+ const agentResolution = asRecord(
99
+ yield* effectTryMaybeAsync(
100
+ () =>
101
+ turnHooks.resolveAgent?.({
102
+ agentId,
103
+ mode: 'fixedThreadMode',
104
+ thread: null,
105
+ threadRef: params.threadId,
106
+ orgRef: params.orgId,
107
+ userRef: params.userId,
108
+ onboardingActive: false,
109
+ linearInstalled: false,
110
+ githubInstalled: params.githubInstalled,
111
+ additionalInstructionSections: mergeInstructionSections(
112
+ dynamicInstructionSections,
113
+ params.additionalInstructionSections,
114
+ ),
115
+ context: (params.context as Record<string, unknown> | null | undefined) ?? null,
116
+ }),
117
+ 'Failed to resolve team-think participant agent.',
118
+ ),
119
+ )
120
+ const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
121
+ const config = agentFactoryConfig.getAgentRuntimeConfig({
126
122
  agentId: resolvedAgentId,
127
- task: runParams.task,
128
- }),
129
- })
130
- const { tools } = yield* buildTeamThinkAgentToolsEffect({
131
- agentId: resolvedAgentId,
132
- workspaceId: params.orgId,
133
- userId: params.userId,
134
- workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
135
- threadId: params.threadId,
136
- githubInstalled: params.githubInstalled,
137
- provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
138
- availableUploads: params.availableUploads,
139
- defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
140
- context: params.context,
141
- toolProviders: params.toolProviders,
142
- })
143
- const agentId_ = config.id || resolvedAgentId
144
- const configuredMaxSteps = config.maxSteps
145
- const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
146
- const agentFactory = getResolvedAgentFactoryConfig().createAgent[agentId_]
147
- if (!agentFactory) {
148
- return yield* new TeamThinkAgentFactoryNotConfiguredError({ agentId: agentId_ })
149
- }
150
- const agent = agentFactory({
151
- mode: 'fixedThreadMode',
152
- tools,
153
- extraInstructions: config.extraInstructions,
154
- maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
155
- stopWhen: [stepCountIs(maxSteps)],
156
- })
157
- const observer = {
158
- run: <T>(fn: () => T | Promise<T>): Promise<T> =>
159
- runPromiseWithCurrentContext(effectTryMaybeAsync(fn, `Team-think participant run failed (${agentId}).`)),
160
- recordError: (error: unknown) => {
161
- aiLogger.error`Team-think participant failed (${agentId}): ${error}`
162
- },
163
- recordAbort: (error: unknown) => {
164
- aiLogger.debug`Team-think participant aborted (${agentId}): ${
165
- error instanceof Error ? error.message : String(error)
166
- }`
167
- },
168
- }
169
- return {
170
- agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
171
- observer,
172
- }
173
- }).pipe(Effect.withSpan('tool.teamThink.buildParticipantAgent')),
174
- )
175
- },
176
- }
123
+ threadType: 'group' as const,
124
+ mode: 'fixedThreadMode',
125
+ onboardingActive: false,
126
+ linearInstalled: false,
127
+ systemWorkspaceDetails: runParams.systemWorkspaceDetails,
128
+ preSeededMemoriesSection: runParams.preSeededMemoriesSection,
129
+ retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
130
+ additionalInstructionSections: mergeInstructionSections(
131
+ dynamicInstructionSections,
132
+ params.additionalInstructionSections,
133
+ readInstructionSections(agentResolution?.additionalInstructionSections),
134
+ ),
135
+ responseGuardSection: buildTeamConsultationResponseGuard({
136
+ agentConfig,
137
+ agentId: resolvedAgentId,
138
+ task: runParams.task,
139
+ }),
140
+ })
141
+ const { tools } = yield* buildTeamThinkAgentToolsEffect(
142
+ {
143
+ agentId: resolvedAgentId,
144
+ workspaceId: params.orgId,
145
+ userId: params.userId,
146
+ workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
147
+ threadId: params.threadId,
148
+ githubInstalled: params.githubInstalled,
149
+ provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
150
+ availableUploads: params.availableUploads,
151
+ defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
152
+ context: params.context,
153
+ toolProviders: params.toolProviders,
154
+ },
155
+ runtimeAdapters,
156
+ )
157
+ const agentId_ = config.id || resolvedAgentId
158
+ const configuredMaxSteps = config.maxSteps
159
+ const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
160
+ const agentFactory = agentFactoryConfig.createAgent[agentId_]
161
+ if (!agentFactory) {
162
+ return yield* new TeamThinkAgentFactoryNotConfiguredError({ agentId: agentId_ })
163
+ }
164
+ const agent = agentFactory({
165
+ mode: 'fixedThreadMode',
166
+ tools,
167
+ extraInstructions: config.extraInstructions,
168
+ maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
169
+ stopWhen: [stepCountIs(maxSteps)],
170
+ })
171
+ const observer = {
172
+ run: <T>(fn: () => T | Promise<T>): Promise<T> =>
173
+ runPromiseWithCurrentContext(
174
+ effectTryMaybeAsync(fn, `Team-think participant run failed (${agentId}).`),
175
+ ),
176
+ recordError: (error: unknown) => {
177
+ aiLogger.error`Team-think participant failed (${agentId}): ${error}`
178
+ },
179
+ recordAbort: (error: unknown) => {
180
+ aiLogger.debug`Team-think participant aborted (${agentId}): ${
181
+ error instanceof Error ? error.message : String(error)
182
+ }`
183
+ },
184
+ }
185
+ return {
186
+ agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
187
+ observer,
188
+ }
189
+ }).pipe(Effect.withSpan('tool.teamThink.buildParticipantAgent')),
190
+ )
191
+ },
192
+ }
177
193
 
178
- return createConsultTeamToolSdk({
179
- historyMessages: params.historyMessages,
180
- latestUserMessageId: params.latestUserMessageId,
181
- availableUploads: params.availableUploads,
182
- defaultRepoSectionsByAgent: params.defaultRepoSectionsByAgent,
183
- systemWorkspaceDetails: params.systemWorkspaceDetails,
184
- getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
185
- retrievedKnowledgeSection: params.retrievedKnowledgeSection,
186
- displayNamesById: agentIdentityOverrides.displayNamesById,
187
- abortSignal: params.abortSignal,
188
- participantRunner,
189
- onReadError: (agentId, error) => {
190
- if (!(error instanceof Error && error.name === 'AbortError')) {
191
- aiLogger.error`UI message read failed for team-think participant ${agentId}: ${error}`
192
- }
193
- },
194
+ return createConsultTeamToolSdk({
195
+ agentConfig,
196
+ historyMessages: params.historyMessages,
197
+ latestUserMessageId: params.latestUserMessageId,
198
+ availableUploads: params.availableUploads,
199
+ defaultRepoSectionsByAgent: params.defaultRepoSectionsByAgent,
200
+ systemWorkspaceDetails: params.systemWorkspaceDetails,
201
+ getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
202
+ retrievedKnowledgeSection: params.retrievedKnowledgeSection,
203
+ displayNamesById: agentIdentityOverrides.displayNamesById,
204
+ abortSignal: params.abortSignal,
205
+ participantRunner,
206
+ onReadError: (agentId, error) => {
207
+ if (!(error instanceof Error && error.name === 'AbortError')) {
208
+ aiLogger.error`UI message read failed for team-think participant ${agentId}: ${error}`
209
+ }
210
+ },
211
+ })
194
212
  })
195
213
  }
@@ -2,12 +2,11 @@ import { ConfigProvider, Option, Schema, Effect, Layer, ManagedRuntime, Redacted
2
2
 
3
3
  import { AiGatewayLive } from '../ai-gateway/ai-gateway'
4
4
  import { EmbeddingCacheLive } from '../ai/embedding-cache'
5
- import { configureLotaLogger, resolveLotaLogLevel, serverLogger } from '../config/logger'
5
+ import { serverLogger } from '../config/logger'
6
6
  import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
7
- import { buildWorkerInfrastructureLayer, setLotaSdkRuntime } from '../effect'
8
- import { DatabaseServiceTag } from '../effect/services'
7
+ import { buildWorkerInfrastructureLayer } from '../effect'
8
+ import { DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
9
9
  import { lotaRuntimeEnvConfig, parseLotaRuntimeConfig, parseWorkerBootstrapEnv } from '../runtime/runtime-config'
10
- import { getConfiguredPluginDatabaseConnector } from '../runtime/runtime-extensions'
11
10
  import { FirecrawlLive } from '../tools/firecrawl-client'
12
11
  import { getErrorMessage } from '../utils/errors'
13
12
 
@@ -85,7 +84,6 @@ function ensureSandboxedWorkerRuntimeConfigured(): Promise<WorkerManagedRuntime>
85
84
  sandboxedWorkerSetupPromise = Effect.runPromise(
86
85
  Effect.gen(function* () {
87
86
  const runtimeConfig = yield* buildSandboxedWorkerRuntimeConfigEffect()
88
- yield* Effect.sync(() => configureLotaLogger(resolveLotaLogLevel(Bun.env.LOG_LEVEL) ?? 'info'))
89
87
 
90
88
  const infrastructureLayer = buildWorkerInfrastructureLayer(runtimeConfig)
91
89
 
@@ -95,10 +93,6 @@ function ensureSandboxedWorkerRuntimeConfigured(): Promise<WorkerManagedRuntime>
95
93
 
96
94
  const managedRuntime = ManagedRuntime.make(fullLayer)
97
95
 
98
- yield* Effect.sync(() => {
99
- setLotaSdkRuntime(managedRuntime)
100
- })
101
-
102
96
  return managedRuntime
103
97
  }),
104
98
  ).catch((error) => {
@@ -137,11 +131,16 @@ export function initializeSandboxedWorkerRuntime(): Promise<WorkerManagedRuntime
137
131
  catch: (error) => toSandboxedWorkerBootstrapError('connect-db', error),
138
132
  })
139
133
 
134
+ const runtimeAdapters = yield* Effect.tryPromise({
135
+ try: () => runtime.runPromise(Effect.service(RuntimeAdaptersServiceTag)),
136
+ catch: (error) => toSandboxedWorkerBootstrapError('initialize', error),
137
+ })
138
+
140
139
  yield* Effect.tryPromise({
141
140
  try: () =>
142
141
  connectWithStartupRetry({
143
142
  connect: () => {
144
- const connectPluginRuntimeDatabases = getConfiguredPluginDatabaseConnector()
143
+ const connectPluginRuntimeDatabases = runtimeAdapters.connectPluginDatabases
145
144
  return connectPluginRuntimeDatabases ? connectPluginRuntimeDatabases() : Promise.resolve()
146
145
  },
147
146
  label: 'sandboxed worker plugin database runtime',
@@ -11,6 +11,7 @@ import { TABLES } from '../db/tables'
11
11
  import { effectTryPromise } from '../effect/helpers'
12
12
  import { DatabaseServiceTag } from '../effect/services'
13
13
  import type { MemoryConsolidationJob } from '../queues/memory-consolidation.queue'
14
+ import { QueueJobServiceTag } from '../services/queue-job.service'
14
15
  import { nowDate, unsafeDateFrom } from '../utils/date-time'
15
16
  import { getErrorMessage } from '../utils/errors'
16
17
  import { initializeSandboxedWorkerRuntime } from './bootstrap'
@@ -59,6 +60,8 @@ const memoryConsolidationDatabaseService: SurrealDBService = await runtime.runPr
59
60
  Effect.service(DatabaseServiceTag),
60
61
  )
61
62
 
63
+ const memoryConsolidationQueueJobService = await runtime.runPromise(Effect.service(QueueJobServiceTag))
64
+
62
65
  function db(): SurrealDBService {
63
66
  return memoryConsolidationDatabaseService
64
67
  }
@@ -395,4 +398,4 @@ const handler = (job: SandboxedJob<MemoryConsolidationJob>) =>
395
398
  ),
396
399
  )
397
400
 
398
- export default createTracedWorkerProcessor('memory-consolidation', handler)
401
+ export default createTracedWorkerProcessor('memory-consolidation', handler, memoryConsolidationQueueJobService)
@@ -4,10 +4,17 @@ import type { Context } from 'effect'
4
4
 
5
5
  import { serverLogger } from '../config/logger'
6
6
  import { effectTryPromise } from '../effect/helpers'
7
- import { DatabaseServiceTag, RuntimeAdaptersServiceTag, RuntimeConfigServiceTag } from '../effect/services'
7
+ import {
8
+ AgentConfigServiceTag,
9
+ DatabaseServiceTag,
10
+ RuntimeAdaptersServiceTag,
11
+ RuntimeConfigServiceTag,
12
+ } from '../effect/services'
8
13
  import type { OrganizationLearningJob } from '../queues/organization-learning.queue'
14
+ import { LotaQueuesServiceTag } from '../queues/queues.service'
9
15
  import { LearnedSkillServiceTag } from '../services/learned-skill.service'
10
16
  import { MemoryServiceTag } from '../services/memory/memory.service'
17
+ import { QueueJobServiceTag } from '../services/queue-job.service'
11
18
  import { SocialChatHistoryServiceTag } from '../services/social-chat-history.service'
12
19
  import { initializeSandboxedWorkerRuntime } from './bootstrap'
13
20
  import { runRegularChatMemoryDigest } from './regular-chat-memory-digest.runner'
@@ -19,14 +26,19 @@ import { createTracedWorkerProcessor } from './worker-utils'
19
26
 
20
27
  const runtime = await initializeSandboxedWorkerRuntime()
21
28
  const resolve = <I, T>(tag: Context.Key<I, T>): Promise<T> => runtime.runPromise(Effect.service(tag))
29
+ const agentConfig = await resolve(AgentConfigServiceTag)
30
+ const queues = await resolve(LotaQueuesServiceTag)
22
31
  const regularChatDigestServices: RegularChatDigestServices = {
32
+ agentConfig,
23
33
  databaseService: await resolve(DatabaseServiceTag),
24
34
  memoryService: await resolve(MemoryServiceTag),
25
35
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
26
36
  runtimeAdapters: await resolve(RuntimeAdaptersServiceTag),
37
+ organizationLearningQueue: queues.organizationLearning,
27
38
  }
28
39
  const workerRuntimeConfig = await resolve(RuntimeConfigServiceTag)
29
40
  const skillExtractionServices: SkillExtractionServices = {
41
+ agentConfig,
30
42
  databaseService: await resolve(DatabaseServiceTag),
31
43
  learnedSkillService: await resolve(LearnedSkillServiceTag),
32
44
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
@@ -34,6 +46,7 @@ const skillExtractionServices: SkillExtractionServices = {
34
46
  embeddingModel: workerRuntimeConfig.aiGateway.embeddingModel,
35
47
  openRouterApiKey: workerRuntimeConfig.aiGateway.openRouterApiKey,
36
48
  }
49
+ const organizationLearningQueueJobService = await resolve(QueueJobServiceTag)
37
50
 
38
51
  // One sandboxed worker handles both organization-learning branches so the
39
52
  // queue can dispatch digest and skill jobs without separate worker images.
@@ -62,4 +75,4 @@ const handler = (job: SandboxedJob<OrganizationLearningJob>) =>
62
75
  ),
63
76
  )
64
77
 
65
- export default createTracedWorkerProcessor('organization-learning', handler)
78
+ export default createTracedWorkerProcessor('organization-learning', handler, organizationLearningQueueJobService)
@@ -1,4 +1,3 @@
1
- import { isAgentName } from '../config/agent-defaults'
2
1
  import { compactWhitespace, readRecord, readString } from '../utils/string'
3
2
  import type { DigestMessage } from './utils/thread-message-query'
4
3
 
@@ -26,7 +25,7 @@ function normalizeFilePartMetadata(part: DigestMessagePart): string | null {
26
25
 
27
26
  function extractAssistantLabel(
28
27
  message: DigestMessageForTranscript,
29
- isKnownAgentName: (value: string) => boolean = isAgentName,
28
+ isKnownAgentName: (value: string) => boolean,
30
29
  ): string {
31
30
  const metadataAgentId =
32
31
  typeof message.metadata?.agentId === 'string' ? message.metadata.agentId.trim().toLowerCase() : ''
@@ -44,11 +43,11 @@ function extractAssistantLabel(
44
43
 
45
44
  export function buildDigestTranscript(params: {
46
45
  messages: DigestMessageForTranscript[]
47
- isKnownAgentName?: (value: string) => boolean
46
+ isKnownAgentName: (value: string) => boolean
48
47
  }): { transcript: string; involvedAgentNames: string[] } {
49
48
  const lines: string[] = []
50
49
  const involvedAgentNames = new Set<string>()
51
- const isKnownAgentName = params.isKnownAgentName ?? isAgentName
50
+ const { isKnownAgentName } = params
52
51
 
53
52
  for (const message of params.messages) {
54
53
  if (message.role !== 'user' && message.role !== 'assistant') continue
@@ -3,17 +3,18 @@ import type { Context } from 'effect'
3
3
  import { BoundQuery } from 'surrealdb'
4
4
  import { z } from 'zod'
5
5
 
6
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
7
+ import { isAgentName } from '../config/agent-defaults'
6
8
  import { serverLogger } from '../config/logger'
7
9
  import { ensureRecordId, recordIdToString } from '../db/record-id'
8
10
  import type { RecordIdRef } from '../db/record-id'
9
11
  import type { SurrealDBService } from '../db/service'
10
12
  import { TABLES } from '../db/tables'
11
13
  import { effectTryPromise } from '../effect/helpers'
12
- import {
13
- clearRegularChatMemoryDigestDeduplicationKey,
14
- enqueueRegularChatMemoryDigest,
14
+ import type {
15
+ OrganizationLearningQueueRuntime,
16
+ RegularChatMemoryDigestJob,
15
17
  } from '../queues/organization-learning.queue'
16
- import type { RegularChatMemoryDigestJob } from '../queues/organization-learning.queue'
17
18
  import { createHelperModelRuntime } from '../runtime/helper-model'
18
19
  import type { LotaRuntimeAdapters, LotaRuntimeBackgroundCursor } from '../runtime/runtime-extensions'
19
20
  import type { MemoryServiceTag } from '../services/memory/memory.service'
@@ -43,10 +44,12 @@ class MemoryDigestError extends Schema.TaggedErrorClass<MemoryDigestError>()('Me
43
44
  const REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS = 10 * 60 * 1000
44
45
  const WorkspaceMemoryRowSchema = z.object({ content: z.string() })
45
46
  export interface RegularChatDigestServices {
47
+ agentConfig: ResolvedAgentConfig
46
48
  databaseService: SurrealDBService
47
49
  memoryService: Context.Service.Shape<typeof MemoryServiceTag>
48
50
  socialChatHistoryService: Context.Service.Shape<typeof SocialChatHistoryServiceTag>
49
51
  runtimeAdapters: LotaRuntimeAdapters
52
+ organizationLearningQueue: OrganizationLearningQueueRuntime
50
53
  }
51
54
 
52
55
  const ExtractedFactSchema = z.object({
@@ -240,7 +243,10 @@ function runRegularChatMemoryDigestEffect(
240
243
  }
241
244
 
242
245
  const combinedMessages = [...threadMessages, ...socialMessages].sort(compareDigestMessageOrder)
243
- const { transcript, involvedAgentNames } = buildDigestTranscript({ messages: combinedMessages })
246
+ const { transcript, involvedAgentNames } = buildDigestTranscript({
247
+ messages: combinedMessages,
248
+ isKnownAgentName: (value: string) => isAgentName(services.agentConfig, value),
249
+ })
244
250
  const existingMemories = yield* effectTryPromise(() =>
245
251
  loadExistingOrganizationMemories(services.databaseService, orgId),
246
252
  )
@@ -331,8 +337,10 @@ function runRegularChatMemoryDigestEffect(
331
337
 
332
338
  const followUpScheduled = hasMoreThreadMessages || hasMoreSocialMessages
333
339
  if (followUpScheduled) {
334
- yield* effectTryPromise(() => clearRegularChatMemoryDigestDeduplicationKey(orgId))
335
- yield* effectTryPromise(() => enqueueRegularChatMemoryDigest({ orgId }))
340
+ yield* effectTryPromise(() =>
341
+ services.organizationLearningQueue.clearRegularChatMemoryDigestDeduplicationKey(orgId),
342
+ )
343
+ yield* effectTryPromise(() => services.organizationLearningQueue.enqueueRegularChatMemoryDigest({ orgId }))
336
344
  }
337
345
 
338
346
  serverLogger.info`Regular chat memory digest completed for ${orgId}: threadMessages=${threadMessages.length}, socialMessages=${socialMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
@@ -350,7 +358,7 @@ export function runRegularChatMemoryDigest(
350
358
  data: RegularChatMemoryDigestJob,
351
359
  services: RegularChatDigestServices,
352
360
  ): Promise<RegularChatDigestRunResult> {
353
- const { databaseService, memoryService, socialChatHistoryService, runtimeAdapters } = services
361
+ const { runtimeAdapters } = services
354
362
  const orgRef = ensureRecordId(data.orgId, TABLES.ORGANIZATION)
355
363
  const orgId = recordIdToString(orgRef, TABLES.ORGANIZATION)
356
364
  const workspaceProvider = runtimeAdapters.workspaceProvider
@@ -367,12 +375,11 @@ export function runRegularChatMemoryDigest(
367
375
  const withMemoryLock = runtimeAdapters.withWorkspaceMemoryLock
368
376
  const runDigest = () =>
369
377
  Effect.runPromise(
370
- runRegularChatMemoryDigestEffect(
371
- { databaseService, memoryService, socialChatHistoryService, runtimeAdapters },
372
- orgRef,
373
- orgId,
374
- workspaceProvider,
375
- ),
378
+ runRegularChatMemoryDigestEffect(services, orgRef, orgId, workspaceProvider) as Effect.Effect<
379
+ RegularChatDigestRunResult,
380
+ never,
381
+ never
382
+ >,
376
383
  )
377
384
 
378
385
  return withMemoryLock ? withMemoryLock(orgId, runDigest) : runDigest()
@@ -1,6 +1,8 @@
1
1
  import type { Context, Cause } from 'effect'
2
2
  import { Effect } from 'effect'
3
3
 
4
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
5
+ import { isAgentName } from '../config/agent-defaults'
4
6
  import { serverLogger } from '../config/logger'
5
7
  import { ensureRecordId, recordIdToString } from '../db/record-id'
6
8
  import type { RecordIdRef } from '../db/record-id'
@@ -27,6 +29,7 @@ const SKILL_EXTRACTION_TIMEOUT_MS = 10 * 60 * 1000
27
29
  const MIN_MESSAGE_THRESHOLD = 10
28
30
 
29
31
  export interface SkillExtractionServices {
32
+ agentConfig: ResolvedAgentConfig
30
33
  databaseService: SurrealDBService
31
34
  learnedSkillService: Context.Service.Shape<typeof LearnedSkillServiceTag>
32
35
  socialChatHistoryService: Context.Service.Shape<typeof SocialChatHistoryServiceTag>
@@ -149,7 +152,10 @@ function runSkillExtractionEffect(
149
152
  }
150
153
 
151
154
  const sortedMessages = [...messages].sort(compareDigestMessageOrder)
152
- const { transcript } = buildDigestTranscript({ messages: sortedMessages })
155
+ const { transcript } = buildDigestTranscript({
156
+ messages: sortedMessages,
157
+ isKnownAgentName: (value: string) => isAgentName(services.agentConfig, value),
158
+ })
153
159
 
154
160
  const existingSkills = yield* services.learnedSkillService.listForOrg(orgId)
155
161
  const existingSkillsSummary =
@@ -301,7 +307,7 @@ export function runSkillExtraction(
301
307
  data: SkillExtractionJob,
302
308
  services: SkillExtractionServices,
303
309
  ): Promise<SkillExtractionRunResult> {
304
- const { databaseService, learnedSkillService, socialChatHistoryService, runtimeAdapters } = services
310
+ const { runtimeAdapters } = services
305
311
  const orgRef = ensureRecordId(data.orgId, TABLES.ORGANIZATION)
306
312
  const orgId = recordIdToString(orgRef, TABLES.ORGANIZATION)
307
313
  const workspaceProvider = runtimeAdapters.workspaceProvider
@@ -317,19 +323,11 @@ export function runSkillExtraction(
317
323
  const withMemoryLock = runtimeAdapters.withWorkspaceMemoryLock
318
324
  const runExtraction = () =>
319
325
  Effect.runPromise(
320
- runSkillExtractionEffect(
321
- {
322
- databaseService,
323
- learnedSkillService,
324
- socialChatHistoryService,
325
- runtimeAdapters,
326
- embeddingModel: services.embeddingModel,
327
- },
328
- orgRef,
329
- orgId,
330
- workspaceProvider,
331
- embeddings,
332
- ),
326
+ runSkillExtractionEffect(services, orgRef, orgId, workspaceProvider, embeddings) as Effect.Effect<
327
+ SkillExtractionRunResult,
328
+ never,
329
+ never
330
+ >,
333
331
  )
334
332
 
335
333
  return withMemoryLock ? withMemoryLock(orgId, runExtraction) : runExtraction()