@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
@@ -4,6 +4,7 @@ import type {
4
4
  PlanArtifactRecord,
5
5
  PlanArtifactSubmission,
6
6
  PlanFailureClass,
7
+ PlanNodeResultSubmission,
7
8
  PlanNodeRunRecord,
8
9
  PlanNodeSpec,
9
10
  PlanNodeSpecRecord,
@@ -14,16 +15,18 @@ import type {
14
15
  PlanDraft,
15
16
  UpstreamHandoff,
16
17
  } from '@lota-sdk/shared'
17
- import { Cause, Context, Effect, Layer, Match } from 'effect'
18
+ import { Cause, Context, Effect, Exit, Layer, Match } from 'effect'
18
19
 
19
20
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
20
21
  import type { RecordIdInput } from '../db/record-id'
21
22
  import { ensureRecordId, recordIdToString } from '../db/record-id'
22
23
  import type { SurrealDBService } from '../db/service'
23
24
  import { TABLES } from '../db/tables'
24
- import { BadRequestError, ConfigurationError, DatabaseError } from '../effect/errors'
25
- import { isPromiseLike } from '../effect/helpers'
25
+ import { BadRequestError, ConfigurationError, DatabaseError, ServiceError } from '../effect/errors'
26
+ import { isPromiseLike, makeEffectTryPromiseWithMessage } from '../effect/helpers'
26
27
  import { AgentConfigServiceTag, DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
28
+ import type { PlanAgentHeartbeatQueueRuntime } from '../queues/plan-agent-heartbeat.queue'
29
+ import { LotaQueuesServiceTag } from '../queues/queues.service'
27
30
  import { resolvePlanNodeExecutionVisibility, shouldPlanNodeUseVisibleTurn } from '../runtime/execution-plan-visibility'
28
31
  import type { LotaRuntimeAdapters } from '../runtime/runtime-extensions'
29
32
  import type { makeAgentExecutorService } from './agent-executor.service'
@@ -51,14 +54,21 @@ interface OwnershipDispatcherDeps {
51
54
  db: SurrealDBService
52
55
  agentConfig: ResolvedAgentConfig
53
56
  runtimeAdapters: LotaRuntimeAdapters
54
- agentExecutor: ReturnType<typeof makeAgentExecutorService>
55
- monitoringWindow: ReturnType<typeof makeMonitoringWindowService>
57
+ agentExecutor: NoContextService<Pick<ReturnType<typeof makeAgentExecutorService>, 'executeNode'>>
58
+ monitoringWindow: NoContextService<Pick<ReturnType<typeof makeMonitoringWindowService>, 'startMonitoringWindow'>>
56
59
  planExecutor: ReturnType<typeof makePlanExecutorService>
57
60
  planRun: ReturnType<typeof makePlanRunService>
58
- pluginExecutor: ReturnType<typeof makePluginExecutorService>
59
- skillResolver: SkillResolverService
60
- systemExecutor: ReturnType<typeof makeSystemExecutorService>
61
- user: ReturnType<typeof makeUserService>
61
+ pluginExecutor: NoContextService<Pick<ReturnType<typeof makePluginExecutorService>, 'executeNode' | 'validateOwner'>>
62
+ skillResolver: NoContextService<Pick<SkillResolverService, 'resolve'>>
63
+ systemExecutor: NoContextService<Pick<ReturnType<typeof makeSystemExecutorService>, 'executeNode' | 'validateOwner'>>
64
+ user: NoContextService<Pick<ReturnType<typeof makeUserService>, 'getUser'>>
65
+ planAgentHeartbeatQueue: PlanAgentHeartbeatQueueRuntime
66
+ }
67
+
68
+ type NoContextService<TService> = {
69
+ [K in keyof TService]: TService[K] extends (...args: infer TArgs) => Effect.Effect<infer A, infer E, unknown>
70
+ ? (...args: TArgs) => Effect.Effect<A, E, never>
71
+ : TService[K]
62
72
  }
63
73
 
64
74
  const STABLE_RUN_STATUSES = new Set(['pending-approval', 'awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
@@ -132,6 +142,8 @@ function toDispatchDatabaseError(message: string, cause: unknown) {
132
142
  return new DatabaseError({ message, cause })
133
143
  }
134
144
 
145
+ const tryDispatchPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
146
+
135
147
  const matchDraftExecutor = (deps: OwnershipDispatcherDeps, node: { id: string; label: string }) =>
136
148
  Match.type<PlanNodeOwner>().pipe(
137
149
  Match.discriminator('executorType')('agent', (owner): PlanValidationIssueInput[] =>
@@ -169,8 +181,9 @@ const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRec
169
181
  return true
170
182
  }
171
183
 
172
- const workspace = yield* Effect.tryPromise(() =>
173
- workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
184
+ const workspace = yield* tryDispatchPromise(
185
+ () => workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
186
+ 'Failed to load workspace for dispatch eligibility.',
174
187
  )
175
188
  if (!workspaceProvider.getLifecycleState) {
176
189
  return true
@@ -179,7 +192,7 @@ const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRec
179
192
  const lifecycleState = yield* Effect.gen(function* () {
180
193
  const result = workspaceProvider.getLifecycleState?.call(workspaceProvider, workspace)
181
194
  if (isPromiseLike(result)) {
182
- return yield* Effect.tryPromise(() => result)
195
+ return yield* tryDispatchPromise(() => result, 'Failed to read workspace lifecycle state.')
183
196
  }
184
197
 
185
198
  return result
@@ -210,7 +223,9 @@ const buildDispatchContextEffect = (
210
223
  ])
211
224
  const userId = thread?.userId ? recordIdToString(thread.userId, TABLES.USER) : undefined
212
225
  const userResult = userId ? yield* Effect.exit(deps.user.getUser(userId)) : undefined
213
- const userName = userResult?._tag === 'Success' ? userResult.value.name : undefined
226
+ const userName = userResult
227
+ ? Exit.match(userResult, { onSuccess: (user) => user.name, onFailure: () => undefined })
228
+ : undefined
214
229
  const nodeSpecsById = new Map(nodeSpecs.map((candidate) => [candidate.nodeId, candidate]))
215
230
  const upstreamHandoffs: UpstreamHandoff[] = nodeRuns
216
231
  .filter(
@@ -274,71 +289,81 @@ const dispatchNodeEffect = (
274
289
  }
275
290
  }
276
291
 
277
- switch (params.nodeSpec.owner.executorType) {
278
- case 'agent':
279
- return yield* deps.agentExecutor.executeNode({
292
+ return yield* Match.value(params.nodeSpec.owner.executorType).pipe(
293
+ Match.when('agent', () =>
294
+ deps.agentExecutor.executeNode({
280
295
  nodeSpec: params.nodeSpec,
281
296
  resolvedInput: params.resolvedInput,
282
297
  inputArtifacts: params.inputArtifacts,
283
298
  context: params.context,
284
299
  executionMode: effectiveExecutionMode,
285
300
  schemaRegistry: params.schemaRegistry,
286
- })
287
- case 'plugin':
288
- return yield* deps.pluginExecutor.executeNode({
301
+ }),
302
+ ),
303
+ Match.when('plugin', () =>
304
+ deps.pluginExecutor.executeNode({
289
305
  nodeSpec: params.nodeSpec,
290
306
  resolvedInput: params.resolvedInput,
291
307
  context: params.context,
292
- })
293
- case 'system':
294
- return yield* deps.systemExecutor.executeNode({
308
+ }),
309
+ ),
310
+ Match.when('system', () =>
311
+ deps.systemExecutor.executeNode({
295
312
  nodeSpec: params.nodeSpec,
296
313
  resolvedInput: params.resolvedInput,
297
314
  context: params.context,
298
- })
299
- case 'skill': {
300
- const resolved = yield* deps.skillResolver.resolve({
301
- skillRef: params.nodeSpec.owner.ref,
302
- organizationId: params.context.organizationId,
303
- })
304
- if (!resolved) {
305
- return yield* new ConfigurationError({
306
- message: `Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`,
315
+ }),
316
+ ),
317
+ Match.when('skill', () =>
318
+ Effect.gen(function* () {
319
+ const resolved = yield* deps.skillResolver.resolve({
320
+ skillRef: params.nodeSpec.owner.ref,
321
+ organizationId: params.context.organizationId,
307
322
  })
308
- }
309
-
310
- if (resolved.executorType === 'agent') {
311
- const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
312
- return yield* deps.agentExecutor.executeNode({
313
- nodeSpec: skillNodeSpec,
323
+ if (!resolved) {
324
+ return yield* new ConfigurationError({
325
+ message: `Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`,
326
+ })
327
+ }
328
+
329
+ if (resolved.executorType === 'agent') {
330
+ const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
331
+ return yield* deps.agentExecutor.executeNode({
332
+ nodeSpec: skillNodeSpec,
333
+ resolvedInput: params.resolvedInput,
334
+ inputArtifacts: params.inputArtifacts,
335
+ context: params.context,
336
+ executionMode: effectiveExecutionMode,
337
+ schemaRegistry: params.schemaRegistry,
338
+ })
339
+ }
340
+
341
+ return yield* deps.pluginExecutor.executeNode({
342
+ nodeSpec: {
343
+ ...params.nodeSpec,
344
+ owner: {
345
+ executorType: 'plugin' as const,
346
+ ref: resolved.ref,
347
+ operation: resolved.operation ?? params.nodeSpec.owner.ref,
348
+ },
349
+ },
314
350
  resolvedInput: params.resolvedInput,
315
- inputArtifacts: params.inputArtifacts,
316
351
  context: params.context,
317
- executionMode: effectiveExecutionMode,
318
- schemaRegistry: params.schemaRegistry,
319
352
  })
320
- }
321
-
322
- return yield* deps.pluginExecutor.executeNode({
323
- nodeSpec: {
324
- ...params.nodeSpec,
325
- owner: {
326
- executorType: 'plugin' as const,
327
- ref: resolved.ref,
328
- operation: resolved.operation ?? params.nodeSpec.owner.ref,
329
- },
330
- },
331
- resolvedInput: params.resolvedInput,
332
- context: params.context,
333
- })
334
- }
335
- case 'user':
336
- return yield* new BadRequestError({
337
- message: `User-owned node "${params.nodeSpec.id}" cannot be auto-dispatched.`,
338
- })
339
- }
353
+ }),
354
+ ),
355
+ Match.when(
356
+ 'user',
357
+ () => new BadRequestError({ message: `User-owned node "${params.nodeSpec.id}" cannot be auto-dispatched.` }),
358
+ ),
359
+ Match.exhaustive,
360
+ )
340
361
  })
341
362
 
363
+ type GraphFullDispatchReadyNode = (
364
+ params: Parameters<typeof dispatchReadyNodeEffect>[1],
365
+ ) => Effect.Effect<PlanNodeResultSubmission, Effect.Error<ReturnType<typeof dispatchReadyNodeEffect>>, never>
366
+
342
367
  const dispatchRunToStableBoundaryEffect = (
343
368
  deps: OwnershipDispatcherDeps,
344
369
  params: { runId: RecordIdInput; emittedBy: string },
@@ -359,20 +384,24 @@ const dispatchRunToStableBoundaryEffect = (
359
384
  const spec = yield* deps.planRun.getPlanSpecById(run.planSpecId)
360
385
 
361
386
  if (spec.executionMode === 'graph-full') {
387
+ const dispatchReadyNodeForGraphFull: GraphFullDispatchReadyNode = (dispatchParams) =>
388
+ dispatchReadyNodeEffect(deps, dispatchParams)
389
+
362
390
  yield* routeGraphFullEffect(
363
391
  { threadId: recordIdToString(run.threadId, TABLES.THREAD), runId: recordIdToString(run.id, TABLES.PLAN_RUN) },
364
392
  {
365
- dispatchReadyNode: (dispatchParams) => dispatchReadyNodeEffect(deps, dispatchParams),
393
+ agentConfig: deps.agentConfig,
394
+ dispatchReadyNode: dispatchReadyNodeForGraphFull,
366
395
  planExecutorService: deps.planExecutor,
367
396
  planRunService: deps.planRun,
397
+ planAgentHeartbeatQueue: deps.planAgentHeartbeatQueue,
368
398
  },
369
399
  ).pipe(
370
- Effect.catch(() =>
371
- Effect.fail(
400
+ Effect.mapError(
401
+ () =>
372
402
  new ConfigurationError({
373
403
  message: `Failed to route graph-full execution for run ${recordIdToString(run.id, TABLES.PLAN_RUN)}.`,
374
404
  }),
375
- ),
376
405
  ),
377
406
  )
378
407
  return yield* serializeRunEffect(deps, run.id)
@@ -403,7 +432,7 @@ const dispatchRunToStableBoundaryEffect = (
403
432
  if (nodeRun.status !== 'running') {
404
433
  return yield* serializeRunEffect(deps, run.id)
405
434
  }
406
- if (shouldPlanNodeUseVisibleTurn(spec, nodeSpecRecord)) {
435
+ if (shouldPlanNodeUseVisibleTurn(deps.agentConfig, spec, nodeSpecRecord)) {
407
436
  return yield* serializeRunEffect(deps, run.id)
408
437
  }
409
438
 
@@ -428,29 +457,33 @@ const dispatchRunToStableBoundaryEffect = (
428
457
  schemaRegistry: spec.schemaRegistry,
429
458
  })
430
459
 
431
- yield* Effect.tryPromise(() =>
432
- deps.planExecutor.submitNodeResult({
433
- threadId: run.threadId,
434
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
435
- nodeId: planNode.id,
436
- emittedBy: planNode.owner.ref,
437
- result,
438
- }),
460
+ yield* tryDispatchPromise(
461
+ () =>
462
+ deps.planExecutor.submitNodeResult({
463
+ threadId: run.threadId,
464
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
465
+ nodeId: planNode.id,
466
+ emittedBy: planNode.owner.ref,
467
+ result,
468
+ }),
469
+ 'Failed to submit plan node result.',
439
470
  )
440
471
  }),
441
472
  )
442
473
 
443
- if (dispatchExit._tag === 'Failure') {
474
+ if (Exit.isFailure(dispatchExit)) {
444
475
  const failure = Cause.squash(dispatchExit.cause)
445
- yield* Effect.tryPromise(() =>
446
- deps.planExecutor.blockNodeOnDispatchFailure({
447
- threadId: run.threadId,
448
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
449
- nodeId: planNode.id,
450
- emittedBy: planNode.owner.ref,
451
- message: formatDispatchError(failure),
452
- failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error: failure }),
453
- }),
476
+ yield* tryDispatchPromise(
477
+ () =>
478
+ deps.planExecutor.blockNodeOnDispatchFailure({
479
+ threadId: run.threadId,
480
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
481
+ nodeId: planNode.id,
482
+ emittedBy: planNode.owner.ref,
483
+ message: formatDispatchError(failure),
484
+ failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error: failure }),
485
+ }),
486
+ 'Failed to block plan node on dispatch failure.',
454
487
  )
455
488
  return yield* serializeRunEffect(deps, run.id)
456
489
  }
@@ -475,7 +508,7 @@ const dispatchReadyNodeEffect = (
475
508
  },
476
509
  ) =>
477
510
  Effect.gen(function* () {
478
- if (shouldPlanNodeUseVisibleTurn(params.spec, params.nodeSpecRecord)) {
511
+ if (shouldPlanNodeUseVisibleTurn(deps.agentConfig, params.spec, params.nodeSpecRecord)) {
479
512
  return yield* new BadRequestError({
480
513
  message: `Node "${params.nodeSpecRecord.nodeId}" requires a visible plan turn and cannot be silently dispatched.`,
481
514
  })
@@ -503,8 +536,12 @@ const dispatchReadyNodeEffect = (
503
536
  })
504
537
  })
505
538
 
506
- function resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
507
- return resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpecRecord)
539
+ function resolveExecutionVisibility(params: {
540
+ agentConfig: ResolvedAgentConfig
541
+ spec: PlanSpecRecord
542
+ nodeSpecRecord: PlanNodeSpecRecord
543
+ }) {
544
+ return resolvePlanNodeExecutionVisibility(params.agentConfig, params.spec, params.nodeSpecRecord)
508
545
  }
509
546
 
510
547
  export function makeOwnershipDispatcherService(deps: OwnershipDispatcherDeps) {
@@ -525,7 +562,7 @@ export function makeOwnershipDispatcherService(deps: OwnershipDispatcherDeps) {
525
562
  return dispatchReadyNodeEffect(deps, params)
526
563
  },
527
564
  resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
528
- return resolveExecutionVisibility(params)
565
+ return resolveExecutionVisibility({ ...params, agentConfig: deps.agentConfig })
529
566
  },
530
567
  dispatchNode(params: {
531
568
  nodeSpec: PlanNodeSpec
@@ -541,14 +578,19 @@ export function makeOwnershipDispatcherService(deps: OwnershipDispatcherDeps) {
541
578
  } as const
542
579
  }
543
580
 
581
+ export interface OwnershipDispatcherService extends ReturnType<typeof makeOwnershipDispatcherService> {}
582
+
544
583
  export class OwnershipDispatcherServiceTag extends Context.Service<
545
584
  OwnershipDispatcherServiceTag,
546
- ReturnType<typeof makeOwnershipDispatcherService>
547
- >()('OwnershipDispatcherService') {}
585
+ OwnershipDispatcherService
586
+ >()('@lota-sdk/core/OwnershipDispatcherService') {}
548
587
 
549
588
  export const OwnershipDispatcherServiceLive = Layer.effect(
550
589
  OwnershipDispatcherServiceTag,
551
590
  Effect.gen(function* () {
591
+ const currentContext = yield* Effect.context()
592
+ const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
593
+ effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
552
594
  const db = yield* DatabaseServiceTag
553
595
  const agentConfig = yield* AgentConfigServiceTag
554
596
  const runtimeAdapters = yield* RuntimeAdaptersServiceTag
@@ -560,18 +602,29 @@ export const OwnershipDispatcherServiceLive = Layer.effect(
560
602
  const skillResolver = yield* SkillResolverServiceTag
561
603
  const systemExecutor = yield* SystemExecutorServiceTag
562
604
  const user = yield* UserServiceTag
605
+ const queues = yield* LotaQueuesServiceTag
606
+ const executeAgentNode = agentExecutor.executeNode as OwnershipDispatcherDeps['agentExecutor']['executeNode']
563
607
  return makeOwnershipDispatcherService({
564
608
  db,
565
609
  agentConfig,
566
610
  runtimeAdapters,
567
- agentExecutor,
568
- monitoringWindow,
611
+ agentExecutor: { executeNode: executeAgentNode },
612
+ monitoringWindow: {
613
+ startMonitoringWindow: (params) => provideCurrentContext(monitoringWindow.startMonitoringWindow(params)),
614
+ },
569
615
  planExecutor,
570
616
  planRun,
571
- pluginExecutor,
572
- skillResolver,
573
- systemExecutor,
574
- user,
617
+ pluginExecutor: {
618
+ validateOwner: (owner, nodeId) => pluginExecutor.validateOwner(owner, nodeId),
619
+ executeNode: (params) => provideCurrentContext(pluginExecutor.executeNode(params)),
620
+ },
621
+ skillResolver: { resolve: (params) => provideCurrentContext(skillResolver.resolve(params)) },
622
+ systemExecutor: {
623
+ validateOwner: (owner, nodeId) => systemExecutor.validateOwner(owner, nodeId),
624
+ executeNode: (params) => provideCurrentContext(systemExecutor.executeNode(params)),
625
+ },
626
+ user: { getUser: (userId) => provideCurrentContext(user.getUser(userId)) },
627
+ planAgentHeartbeatQueue: queues.planAgentHeartbeat,
575
628
  })
576
629
  }),
577
630
  )
@@ -1,10 +1,13 @@
1
1
  import { Context, Schema, Effect, Layer } from 'effect'
2
2
 
3
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
3
4
  import { serverLogger } from '../../config/logger'
4
5
  import { ensureRecordId } from '../../db/record-id'
5
6
  import { TABLES } from '../../db/tables'
6
7
  import { effectTryPromise } from '../../effect/helpers'
7
- import { RedisServiceTag } from '../../effect/services'
8
+ import { AgentConfigServiceTag, RedisServiceTag } from '../../effect/services'
9
+ import type { PlanAgentHeartbeatQueueRuntime } from '../../queues/plan-agent-heartbeat.queue'
10
+ import { LotaQueuesServiceTag } from '../../queues/queues.service'
8
11
  import type { RedisConnectionManager } from '../../redis/connection'
9
12
  import { withLeaseLock } from '../../redis/redis-lease-lock'
10
13
  import { resolvePlanNodeExecutionVisibility } from '../../runtime/execution-plan-visibility'
@@ -39,30 +42,40 @@ class PlanAgentHeartbeatError extends Schema.TaggedErrorClass<PlanAgentHeartbeat
39
42
  cause: Schema.Defect,
40
43
  }) {}
41
44
 
42
- function tryHeartbeatPromise<A>(
45
+ function tryHeartbeatPromise<A, R = never>(
43
46
  operation: string,
44
- thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
45
- ): Effect.Effect<A, PlanAgentHeartbeatError> {
47
+ thunk: () => PromiseLike<A> | Effect.Effect<A, unknown, R>,
48
+ ): Effect.Effect<A, PlanAgentHeartbeatError, R> {
46
49
  return effectTryPromise(thunk, (cause) => new PlanAgentHeartbeatError({ operation, cause }))
47
50
  }
48
51
 
49
- function heartbeatServiceEffect<A, E>(
52
+ function heartbeatServiceEffect<A, E, R = never>(
50
53
  operation: string,
51
- effect: Effect.Effect<A, E>,
52
- ): Effect.Effect<A, PlanAgentHeartbeatError> {
54
+ effect: Effect.Effect<A, E, R>,
55
+ ): Effect.Effect<A, PlanAgentHeartbeatError, R> {
53
56
  return effect.pipe(Effect.mapError((cause) => new PlanAgentHeartbeatError({ operation, cause })))
54
57
  }
55
58
 
56
59
  interface PlanAgentHeartbeatDeps {
60
+ agentConfig: ResolvedAgentConfig
57
61
  redis: RedisConnectionManager
58
62
  planAgentQueryService: ReturnType<typeof makePlanAgentQueryService>
59
63
  planExecutorService: ReturnType<typeof makePlanExecutorService>
60
64
  planRunService: ReturnType<typeof makePlanRunService>
61
65
  threadService: ReturnType<typeof makeThreadService>
66
+ planAgentHeartbeatQueue: PlanAgentHeartbeatQueueRuntime
62
67
  }
63
68
 
64
69
  export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
65
- const { planExecutorService, planRunService, redis, planAgentQueryService, threadService } = deps
70
+ const {
71
+ agentConfig,
72
+ planExecutorService,
73
+ planRunService,
74
+ redis,
75
+ planAgentQueryService,
76
+ threadService,
77
+ planAgentHeartbeatQueue,
78
+ } = deps
66
79
 
67
80
  const wakeNodeEffect = (params: {
68
81
  organizationId: string
@@ -71,7 +84,7 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
71
84
  nodeId: string
72
85
  agentId: string
73
86
  reason: string
74
- }): Effect.Effect<boolean, PlanAgentHeartbeatError> =>
87
+ }): Effect.Effect<boolean, PlanAgentHeartbeatError, unknown> =>
75
88
  Effect.gen(function* () {
76
89
  const threadRef = ensureRecordId(params.threadId, TABLES.THREAD)
77
90
  yield* heartbeatServiceEffect(
@@ -125,7 +138,7 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
125
138
  return false
126
139
  }
127
140
 
128
- const visibility = resolvePlanNodeExecutionVisibility(spec, nodeSpec)
141
+ const visibility = resolvePlanNodeExecutionVisibility(agentConfig, spec, nodeSpec)
129
142
  if (visibility !== 'visible') {
130
143
  return false
131
144
  }
@@ -153,12 +166,9 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
153
166
  ).pipe(Effect.mapError((cause) => new PlanAgentHeartbeatError({ operation: 'wake-node-lock', cause })))
154
167
  })
155
168
 
156
- const sweepEffect = (params?: { organizationId?: string }): Effect.Effect<void, PlanAgentHeartbeatError> =>
169
+ const sweepEffect = (params?: { organizationId?: string }): Effect.Effect<void, PlanAgentHeartbeatError, unknown> =>
157
170
  Effect.gen(function* () {
158
- const { enqueuePlanAgentHeartbeatWake } = yield* tryHeartbeatPromise(
159
- 'import-heartbeat-queue',
160
- () => import('../../queues/plan-agent-heartbeat.queue'),
161
- )
171
+ const enqueuePlanAgentHeartbeatWake = planAgentHeartbeatQueue.enqueuePlanAgentHeartbeatWake
162
172
  const [actionable, recentlyUnblocked, approachingDeadlines] = yield* Effect.all([
163
173
  heartbeatServiceEffect(
164
174
  'get-actionable-nodes-for-agent',
@@ -213,22 +223,26 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
213
223
  export class PlanAgentHeartbeatServiceTag extends Context.Service<
214
224
  PlanAgentHeartbeatServiceTag,
215
225
  ReturnType<typeof makePlanAgentHeartbeatService>
216
- >()('PlanAgentHeartbeatService') {}
226
+ >()('@lota-sdk/core/PlanAgentHeartbeatService') {}
217
227
 
218
228
  export const PlanAgentHeartbeatServiceLive = Layer.effect(
219
229
  PlanAgentHeartbeatServiceTag,
220
230
  Effect.gen(function* () {
231
+ const agentConfig = yield* AgentConfigServiceTag
221
232
  const redis = yield* RedisServiceTag
222
233
  const planAgentQueryService = yield* PlanAgentQueryServiceTag
223
234
  const planRunService = yield* PlanRunServiceTag
224
235
  const planExecutor = yield* PlanExecutorServiceTag
225
236
  const threadSvc = yield* ThreadServiceTag
237
+ const queues = yield* LotaQueuesServiceTag
226
238
  return makePlanAgentHeartbeatService({
239
+ agentConfig,
227
240
  redis,
228
241
  planAgentQueryService,
229
242
  planExecutorService: planExecutor,
230
243
  planRunService,
231
244
  threadService: threadSvc,
245
+ planAgentHeartbeatQueue: queues.planAgentHeartbeat,
232
246
  })
233
247
  }),
234
248
  )
@@ -3,12 +3,13 @@ import { PlanRunSchema } from '@lota-sdk/shared'
3
3
  import { Context, Schema, Effect, Layer } from 'effect'
4
4
  import { BoundQuery } from 'surrealdb'
5
5
 
6
+ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
6
7
  import type { RecordIdInput } from '../../db/record-id'
7
8
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
8
9
  import type { SurrealDBService } from '../../db/service'
9
10
  import { TABLES } from '../../db/tables'
10
11
  import { effectTryPromise } from '../../effect/helpers'
11
- import { DatabaseServiceTag } from '../../effect/services'
12
+ import { AgentConfigServiceTag, DatabaseServiceTag } from '../../effect/services'
12
13
  import { resolvePlanNodeExecutionVisibility } from '../../runtime/execution-plan-visibility'
13
14
  import { nowDate, unsafeDateFrom } from '../../utils/date-time'
14
15
  import { evaluateDeadline } from './plan-deadline.service'
@@ -53,6 +54,7 @@ export interface RecentlyUnblockedNode {
53
54
  }
54
55
 
55
56
  function isVisibleAgentNode(params: {
57
+ agentConfig: ResolvedAgentConfig
56
58
  nodeSpec: PlanNodeSpecRecord
57
59
  spec: PlanSpecRecord
58
60
  }): { agentId: string; visibility: PlanExecutionVisibility } | null {
@@ -60,7 +62,7 @@ function isVisibleAgentNode(params: {
60
62
  return null
61
63
  }
62
64
 
63
- const visibility = resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpec)
65
+ const visibility = resolvePlanNodeExecutionVisibility(params.agentConfig, params.spec, params.nodeSpec)
64
66
  const isVisible = visibility === 'visible'
65
67
  if (!isVisible) {
66
68
  return null
@@ -93,12 +95,13 @@ function queryServiceEffect<A, E>(
93
95
  }
94
96
 
95
97
  interface PlanAgentQueryDeps {
98
+ agentConfig: ResolvedAgentConfig
96
99
  db: SurrealDBService
97
100
  planRunService: ReturnType<typeof makePlanRunService>
98
101
  }
99
102
 
100
103
  export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
101
- const { db, planRunService } = deps
104
+ const { agentConfig, db, planRunService } = deps
102
105
 
103
106
  function listActiveRunsEffect(organizationId?: RecordIdInput): Effect.Effect<PlanRunRecord[], PlanAgentQueryError> {
104
107
  const bindings = {
@@ -138,7 +141,7 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
138
141
  if (!ACTIONABLE_NODE_STATUSES.has(nodeRun.status)) continue
139
142
  const nodeSpec = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
140
143
  if (!nodeSpec) continue
141
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
144
+ const visibleTarget = isVisibleAgentNode({ agentConfig, nodeSpec, spec })
142
145
  if (!visibleTarget) continue
143
146
  if (params.agentId && params.agentId !== visibleTarget.agentId) continue
144
147
  actionable.push({
@@ -167,7 +170,7 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
167
170
  continue
168
171
  }
169
172
 
170
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
173
+ const visibleTarget = isVisibleAgentNode({ agentConfig, nodeSpec, spec })
171
174
  if (!visibleTarget) {
172
175
  continue
173
176
  }
@@ -226,7 +229,7 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
226
229
  continue
227
230
  }
228
231
 
229
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
232
+ const visibleTarget = isVisibleAgentNode({ agentConfig, nodeSpec, spec })
230
233
  matches.push({
231
234
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
232
235
  threadId: recordIdToString(run.threadId, TABLES.THREAD),
@@ -276,7 +279,7 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
276
279
  continue
277
280
  }
278
281
 
279
- const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
282
+ const visibleTarget = isVisibleAgentNode({ agentConfig, nodeSpec, spec })
280
283
  if (!visibleTarget) {
281
284
  continue
282
285
  }
@@ -310,13 +313,14 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
310
313
  export class PlanAgentQueryServiceTag extends Context.Service<
311
314
  PlanAgentQueryServiceTag,
312
315
  ReturnType<typeof makePlanAgentQueryService>
313
- >()('PlanAgentQueryService') {}
316
+ >()('@lota-sdk/core/PlanAgentQueryService') {}
314
317
 
315
318
  export const PlanAgentQueryServiceLive = Layer.effect(
316
319
  PlanAgentQueryServiceTag,
317
320
  Effect.gen(function* () {
321
+ const agentConfig = yield* AgentConfigServiceTag
318
322
  const db = yield* DatabaseServiceTag
319
323
  const planRunService = yield* PlanRunServiceTag
320
- return makePlanAgentQueryService({ db, planRunService })
324
+ return makePlanAgentQueryService({ agentConfig, db, planRunService })
321
325
  }),
322
326
  )