@lota-sdk/core 0.4.9 → 0.4.10

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 (158) 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 +38 -10
  4. package/src/config/agent-defaults.ts +22 -9
  5. package/src/config/agent-types.ts +1 -1
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/index.ts +0 -1
  8. package/src/config/logger.ts +20 -7
  9. package/src/config/thread-defaults.ts +12 -4
  10. package/src/create-runtime.ts +69 -656
  11. package/src/db/memory-query-builder.ts +2 -1
  12. package/src/db/memory-store.ts +29 -20
  13. package/src/db/memory.ts +188 -195
  14. package/src/db/service-normalization.ts +97 -64
  15. package/src/db/service.ts +706 -538
  16. package/src/db/startup.ts +30 -19
  17. package/src/effect/awaitable-effect.ts +46 -37
  18. package/src/effect/helpers.ts +30 -5
  19. package/src/effect/index.ts +7 -5
  20. package/src/effect/layers.ts +82 -72
  21. package/src/effect/runtime.ts +18 -3
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -66
  24. package/src/index.ts +13 -11
  25. package/src/queues/autonomous-job.queue.ts +59 -71
  26. package/src/queues/context-compaction.queue.ts +6 -18
  27. package/src/queues/delayed-node-promotion.queue.ts +9 -17
  28. package/src/queues/organization-learning.queue.ts +17 -4
  29. package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
  30. package/src/queues/plan-scheduler.queue.ts +6 -18
  31. package/src/queues/post-chat-memory.queue.ts +6 -18
  32. package/src/queues/queue-factory.ts +128 -50
  33. package/src/queues/title-generation.queue.ts +6 -17
  34. package/src/redis/connection.ts +181 -164
  35. package/src/redis/runtime-connection.ts +13 -3
  36. package/src/redis/stream-context.ts +17 -9
  37. package/src/runtime/agent-runtime-policy.ts +1 -1
  38. package/src/runtime/agent-stream-helpers.ts +15 -11
  39. package/src/runtime/chat-run-orchestration.ts +1 -1
  40. package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
  41. package/src/runtime/context-compaction/context-compaction.ts +126 -82
  42. package/src/runtime/domain-layer.ts +192 -0
  43. package/src/runtime/graph-designer.ts +15 -7
  44. package/src/runtime/helper-model.ts +8 -4
  45. package/src/runtime/index.ts +0 -1
  46. package/src/runtime/memory/memory-block.ts +19 -9
  47. package/src/runtime/memory/memory-pipeline.ts +53 -66
  48. package/src/runtime/memory/memory-scope.ts +33 -29
  49. package/src/runtime/plugin-resolution.ts +33 -54
  50. package/src/runtime/post-turn-side-effects.ts +6 -26
  51. package/src/runtime/retrieval-adapters.ts +4 -4
  52. package/src/runtime/runtime-accessors.ts +92 -0
  53. package/src/runtime/runtime-config.ts +3 -3
  54. package/src/runtime/runtime-extensions.ts +20 -9
  55. package/src/runtime/runtime-lifecycle.ts +124 -0
  56. package/src/runtime/runtime-services.ts +386 -0
  57. package/src/runtime/runtime-token.ts +47 -0
  58. package/src/runtime/social-chat/social-chat-agent-runner.ts +7 -5
  59. package/src/runtime/social-chat/social-chat-history.ts +21 -12
  60. package/src/runtime/social-chat/social-chat.ts +401 -365
  61. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
  62. package/src/runtime/thread-turn-context.ts +21 -27
  63. package/src/services/agent-activity.service.ts +1 -1
  64. package/src/services/agent-executor.service.ts +179 -187
  65. package/src/services/artifact.service.ts +10 -5
  66. package/src/services/attachment.service.ts +35 -1
  67. package/src/services/autonomous-job.service.ts +58 -56
  68. package/src/services/background-work.service.ts +54 -0
  69. package/src/services/chat-run-registry.service.ts +3 -1
  70. package/src/services/context-compaction.service.ts +1 -1
  71. package/src/services/document-chunk.service.ts +8 -17
  72. package/src/services/execution-plan/execution-plan-graph.ts +74 -52
  73. package/src/services/execution-plan/execution-plan.service.ts +1 -1
  74. package/src/services/feedback-loop.service.ts +1 -1
  75. package/src/services/global-orchestrator.service.ts +33 -10
  76. package/src/services/graph-full-routing.ts +44 -33
  77. package/src/services/index.ts +1 -0
  78. package/src/services/institutional-memory.service.ts +8 -17
  79. package/src/services/learned-skill.service.ts +38 -35
  80. package/src/services/memory/memory-errors.ts +27 -0
  81. package/src/services/memory/memory-org-memory.ts +14 -3
  82. package/src/services/memory/memory-preseeded.ts +10 -4
  83. package/src/services/memory/memory-utils.ts +2 -1
  84. package/src/services/memory/memory.service.ts +26 -44
  85. package/src/services/memory/rerank.service.ts +3 -11
  86. package/src/services/monitoring-window.service.ts +1 -1
  87. package/src/services/mutating-approval.service.ts +1 -1
  88. package/src/services/node-workspace.service.ts +2 -2
  89. package/src/services/notification.service.ts +16 -4
  90. package/src/services/organization-member.service.ts +1 -1
  91. package/src/services/organization.service.ts +34 -51
  92. package/src/services/ownership-dispatcher.service.ts +132 -90
  93. package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
  94. package/src/services/plan/plan-agent-query.service.ts +1 -1
  95. package/src/services/plan/plan-approval.service.ts +52 -48
  96. package/src/services/plan/plan-artifact.service.ts +2 -2
  97. package/src/services/plan/plan-builder.service.ts +2 -2
  98. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  99. package/src/services/plan/plan-compiler.service.ts +1 -1
  100. package/src/services/plan/plan-completion-side-effects.ts +18 -24
  101. package/src/services/plan/plan-coordination.service.ts +1 -1
  102. package/src/services/plan/plan-cycle.service.ts +171 -164
  103. package/src/services/plan/plan-deadline.service.ts +290 -304
  104. package/src/services/plan/plan-event-delivery.service.ts +44 -39
  105. package/src/services/plan/plan-executor-graph.ts +114 -67
  106. package/src/services/plan/plan-executor-helpers.ts +60 -75
  107. package/src/services/plan/plan-executor.service.ts +550 -467
  108. package/src/services/plan/plan-run.service.ts +12 -19
  109. package/src/services/plan/plan-scheduler.service.ts +27 -33
  110. package/src/services/plan/plan-template.service.ts +1 -1
  111. package/src/services/plan/plan-transaction-events.ts +8 -5
  112. package/src/services/plan/plan-validator.service.ts +1 -1
  113. package/src/services/plan/plan-workspace.service.ts +17 -11
  114. package/src/services/plugin-executor.service.ts +26 -21
  115. package/src/services/quality-metrics.service.ts +1 -1
  116. package/src/services/queue-job.service.ts +8 -17
  117. package/src/services/recent-activity-title.service.ts +17 -9
  118. package/src/services/recent-activity.service.ts +1 -1
  119. package/src/services/skill-resolver.service.ts +1 -1
  120. package/src/services/social-chat-history.service.ts +37 -20
  121. package/src/services/system-executor.service.ts +25 -20
  122. package/src/services/thread/thread-bootstrap.ts +26 -10
  123. package/src/services/thread/thread-listing.ts +2 -1
  124. package/src/services/thread/thread-memory-block.ts +18 -5
  125. package/src/services/thread/thread-message.service.ts +24 -8
  126. package/src/services/thread/thread-title.service.ts +1 -1
  127. package/src/services/thread/thread-turn-execution.ts +1 -1
  128. package/src/services/thread/thread-turn-preparation.service.ts +18 -16
  129. package/src/services/thread/thread-turn-streaming.ts +12 -11
  130. package/src/services/thread/thread-turn.ts +43 -10
  131. package/src/services/thread/thread.service.ts +11 -2
  132. package/src/services/user.service.ts +1 -1
  133. package/src/services/write-intent-validator.service.ts +1 -1
  134. package/src/storage/attachment-storage.service.ts +7 -4
  135. package/src/storage/generated-document-storage.service.ts +1 -1
  136. package/src/system-agents/context-compaction.agent.ts +1 -1
  137. package/src/system-agents/helper-agent-options.ts +1 -1
  138. package/src/system-agents/memory-reranker.agent.ts +1 -1
  139. package/src/system-agents/memory.agent.ts +1 -1
  140. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  141. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  142. package/src/system-agents/skill-extractor.agent.ts +1 -1
  143. package/src/system-agents/skill-manager.agent.ts +1 -1
  144. package/src/system-agents/title-generator.agent.ts +1 -1
  145. package/src/tools/execution-plan.tool.ts +28 -17
  146. package/src/tools/fetch-webpage.tool.ts +20 -13
  147. package/src/tools/firecrawl-client.ts +13 -3
  148. package/src/tools/plan-approval.tool.ts +9 -1
  149. package/src/tools/search-web.tool.ts +16 -9
  150. package/src/tools/team-think.tool.ts +2 -2
  151. package/src/utils/async.ts +15 -6
  152. package/src/utils/errors.ts +27 -15
  153. package/src/workers/bootstrap.ts +25 -48
  154. package/src/workers/organization-learning.worker.ts +1 -1
  155. package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
  156. package/src/workers/worker-utils.ts +20 -2
  157. package/src/config/search.ts +0 -3
  158. 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,15 +15,15 @@ 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'
27
28
  import { resolvePlanNodeExecutionVisibility, shouldPlanNodeUseVisibleTurn } from '../runtime/execution-plan-visibility'
28
29
  import type { LotaRuntimeAdapters } from '../runtime/runtime-extensions'
@@ -51,14 +52,20 @@ interface OwnershipDispatcherDeps {
51
52
  db: SurrealDBService
52
53
  agentConfig: ResolvedAgentConfig
53
54
  runtimeAdapters: LotaRuntimeAdapters
54
- agentExecutor: ReturnType<typeof makeAgentExecutorService>
55
- monitoringWindow: ReturnType<typeof makeMonitoringWindowService>
55
+ agentExecutor: NoContextService<Pick<ReturnType<typeof makeAgentExecutorService>, 'executeNode'>>
56
+ monitoringWindow: NoContextService<Pick<ReturnType<typeof makeMonitoringWindowService>, 'startMonitoringWindow'>>
56
57
  planExecutor: ReturnType<typeof makePlanExecutorService>
57
58
  planRun: ReturnType<typeof makePlanRunService>
58
- pluginExecutor: ReturnType<typeof makePluginExecutorService>
59
- skillResolver: SkillResolverService
60
- systemExecutor: ReturnType<typeof makeSystemExecutorService>
61
- user: ReturnType<typeof makeUserService>
59
+ pluginExecutor: NoContextService<Pick<ReturnType<typeof makePluginExecutorService>, 'executeNode' | 'validateOwner'>>
60
+ skillResolver: NoContextService<Pick<SkillResolverService, 'resolve'>>
61
+ systemExecutor: NoContextService<Pick<ReturnType<typeof makeSystemExecutorService>, 'executeNode' | 'validateOwner'>>
62
+ user: NoContextService<Pick<ReturnType<typeof makeUserService>, 'getUser'>>
63
+ }
64
+
65
+ type NoContextService<TService> = {
66
+ [K in keyof TService]: TService[K] extends (...args: infer TArgs) => Effect.Effect<infer A, infer E, unknown>
67
+ ? (...args: TArgs) => Effect.Effect<A, E, never>
68
+ : TService[K]
62
69
  }
63
70
 
64
71
  const STABLE_RUN_STATUSES = new Set(['pending-approval', 'awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
@@ -132,6 +139,8 @@ function toDispatchDatabaseError(message: string, cause: unknown) {
132
139
  return new DatabaseError({ message, cause })
133
140
  }
134
141
 
142
+ const tryDispatchPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
143
+
135
144
  const matchDraftExecutor = (deps: OwnershipDispatcherDeps, node: { id: string; label: string }) =>
136
145
  Match.type<PlanNodeOwner>().pipe(
137
146
  Match.discriminator('executorType')('agent', (owner): PlanValidationIssueInput[] =>
@@ -169,8 +178,9 @@ const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRec
169
178
  return true
170
179
  }
171
180
 
172
- const workspace = yield* Effect.tryPromise(() =>
173
- workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
181
+ const workspace = yield* tryDispatchPromise(
182
+ () => workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
183
+ 'Failed to load workspace for dispatch eligibility.',
174
184
  )
175
185
  if (!workspaceProvider.getLifecycleState) {
176
186
  return true
@@ -179,7 +189,7 @@ const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRec
179
189
  const lifecycleState = yield* Effect.gen(function* () {
180
190
  const result = workspaceProvider.getLifecycleState?.call(workspaceProvider, workspace)
181
191
  if (isPromiseLike(result)) {
182
- return yield* Effect.tryPromise(() => result)
192
+ return yield* tryDispatchPromise(() => result, 'Failed to read workspace lifecycle state.')
183
193
  }
184
194
 
185
195
  return result
@@ -210,7 +220,9 @@ const buildDispatchContextEffect = (
210
220
  ])
211
221
  const userId = thread?.userId ? recordIdToString(thread.userId, TABLES.USER) : undefined
212
222
  const userResult = userId ? yield* Effect.exit(deps.user.getUser(userId)) : undefined
213
- const userName = userResult?._tag === 'Success' ? userResult.value.name : undefined
223
+ const userName = userResult
224
+ ? Exit.match(userResult, { onSuccess: (user) => user.name, onFailure: () => undefined })
225
+ : undefined
214
226
  const nodeSpecsById = new Map(nodeSpecs.map((candidate) => [candidate.nodeId, candidate]))
215
227
  const upstreamHandoffs: UpstreamHandoff[] = nodeRuns
216
228
  .filter(
@@ -274,71 +286,81 @@ const dispatchNodeEffect = (
274
286
  }
275
287
  }
276
288
 
277
- switch (params.nodeSpec.owner.executorType) {
278
- case 'agent':
279
- return yield* deps.agentExecutor.executeNode({
289
+ return yield* Match.value(params.nodeSpec.owner.executorType).pipe(
290
+ Match.when('agent', () =>
291
+ deps.agentExecutor.executeNode({
280
292
  nodeSpec: params.nodeSpec,
281
293
  resolvedInput: params.resolvedInput,
282
294
  inputArtifacts: params.inputArtifacts,
283
295
  context: params.context,
284
296
  executionMode: effectiveExecutionMode,
285
297
  schemaRegistry: params.schemaRegistry,
286
- })
287
- case 'plugin':
288
- return yield* deps.pluginExecutor.executeNode({
298
+ }),
299
+ ),
300
+ Match.when('plugin', () =>
301
+ deps.pluginExecutor.executeNode({
289
302
  nodeSpec: params.nodeSpec,
290
303
  resolvedInput: params.resolvedInput,
291
304
  context: params.context,
292
- })
293
- case 'system':
294
- return yield* deps.systemExecutor.executeNode({
305
+ }),
306
+ ),
307
+ Match.when('system', () =>
308
+ deps.systemExecutor.executeNode({
295
309
  nodeSpec: params.nodeSpec,
296
310
  resolvedInput: params.resolvedInput,
297
311
  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.`,
312
+ }),
313
+ ),
314
+ Match.when('skill', () =>
315
+ Effect.gen(function* () {
316
+ const resolved = yield* deps.skillResolver.resolve({
317
+ skillRef: params.nodeSpec.owner.ref,
318
+ organizationId: params.context.organizationId,
307
319
  })
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,
320
+ if (!resolved) {
321
+ return yield* new ConfigurationError({
322
+ message: `Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`,
323
+ })
324
+ }
325
+
326
+ if (resolved.executorType === 'agent') {
327
+ const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
328
+ return yield* deps.agentExecutor.executeNode({
329
+ nodeSpec: skillNodeSpec,
330
+ resolvedInput: params.resolvedInput,
331
+ inputArtifacts: params.inputArtifacts,
332
+ context: params.context,
333
+ executionMode: effectiveExecutionMode,
334
+ schemaRegistry: params.schemaRegistry,
335
+ })
336
+ }
337
+
338
+ return yield* deps.pluginExecutor.executeNode({
339
+ nodeSpec: {
340
+ ...params.nodeSpec,
341
+ owner: {
342
+ executorType: 'plugin' as const,
343
+ ref: resolved.ref,
344
+ operation: resolved.operation ?? params.nodeSpec.owner.ref,
345
+ },
346
+ },
314
347
  resolvedInput: params.resolvedInput,
315
- inputArtifacts: params.inputArtifacts,
316
348
  context: params.context,
317
- executionMode: effectiveExecutionMode,
318
- schemaRegistry: params.schemaRegistry,
319
349
  })
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
- }
350
+ }),
351
+ ),
352
+ Match.when(
353
+ 'user',
354
+ () => new BadRequestError({ message: `User-owned node "${params.nodeSpec.id}" cannot be auto-dispatched.` }),
355
+ ),
356
+ Match.exhaustive,
357
+ )
340
358
  })
341
359
 
360
+ type GraphFullDispatchReadyNode = (
361
+ params: Parameters<typeof dispatchReadyNodeEffect>[1],
362
+ ) => Effect.Effect<PlanNodeResultSubmission, Effect.Error<ReturnType<typeof dispatchReadyNodeEffect>>, never>
363
+
342
364
  const dispatchRunToStableBoundaryEffect = (
343
365
  deps: OwnershipDispatcherDeps,
344
366
  params: { runId: RecordIdInput; emittedBy: string },
@@ -359,20 +381,22 @@ const dispatchRunToStableBoundaryEffect = (
359
381
  const spec = yield* deps.planRun.getPlanSpecById(run.planSpecId)
360
382
 
361
383
  if (spec.executionMode === 'graph-full') {
384
+ const dispatchReadyNodeForGraphFull: GraphFullDispatchReadyNode = (dispatchParams) =>
385
+ dispatchReadyNodeEffect(deps, dispatchParams)
386
+
362
387
  yield* routeGraphFullEffect(
363
388
  { threadId: recordIdToString(run.threadId, TABLES.THREAD), runId: recordIdToString(run.id, TABLES.PLAN_RUN) },
364
389
  {
365
- dispatchReadyNode: (dispatchParams) => dispatchReadyNodeEffect(deps, dispatchParams),
390
+ dispatchReadyNode: dispatchReadyNodeForGraphFull,
366
391
  planExecutorService: deps.planExecutor,
367
392
  planRunService: deps.planRun,
368
393
  },
369
394
  ).pipe(
370
- Effect.catch(() =>
371
- Effect.fail(
395
+ Effect.mapError(
396
+ () =>
372
397
  new ConfigurationError({
373
398
  message: `Failed to route graph-full execution for run ${recordIdToString(run.id, TABLES.PLAN_RUN)}.`,
374
399
  }),
375
- ),
376
400
  ),
377
401
  )
378
402
  return yield* serializeRunEffect(deps, run.id)
@@ -428,29 +452,33 @@ const dispatchRunToStableBoundaryEffect = (
428
452
  schemaRegistry: spec.schemaRegistry,
429
453
  })
430
454
 
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
- }),
455
+ yield* tryDispatchPromise(
456
+ () =>
457
+ deps.planExecutor.submitNodeResult({
458
+ threadId: run.threadId,
459
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
460
+ nodeId: planNode.id,
461
+ emittedBy: planNode.owner.ref,
462
+ result,
463
+ }),
464
+ 'Failed to submit plan node result.',
439
465
  )
440
466
  }),
441
467
  )
442
468
 
443
- if (dispatchExit._tag === 'Failure') {
469
+ if (Exit.isFailure(dispatchExit)) {
444
470
  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
- }),
471
+ yield* tryDispatchPromise(
472
+ () =>
473
+ deps.planExecutor.blockNodeOnDispatchFailure({
474
+ threadId: run.threadId,
475
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
476
+ nodeId: planNode.id,
477
+ emittedBy: planNode.owner.ref,
478
+ message: formatDispatchError(failure),
479
+ failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error: failure }),
480
+ }),
481
+ 'Failed to block plan node on dispatch failure.',
454
482
  )
455
483
  return yield* serializeRunEffect(deps, run.id)
456
484
  }
@@ -541,14 +569,19 @@ export function makeOwnershipDispatcherService(deps: OwnershipDispatcherDeps) {
541
569
  } as const
542
570
  }
543
571
 
572
+ export interface OwnershipDispatcherService extends ReturnType<typeof makeOwnershipDispatcherService> {}
573
+
544
574
  export class OwnershipDispatcherServiceTag extends Context.Service<
545
575
  OwnershipDispatcherServiceTag,
546
- ReturnType<typeof makeOwnershipDispatcherService>
547
- >()('OwnershipDispatcherService') {}
576
+ OwnershipDispatcherService
577
+ >()('@lota-sdk/core/OwnershipDispatcherService') {}
548
578
 
549
579
  export const OwnershipDispatcherServiceLive = Layer.effect(
550
580
  OwnershipDispatcherServiceTag,
551
581
  Effect.gen(function* () {
582
+ const currentContext = yield* Effect.context()
583
+ const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
584
+ effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
552
585
  const db = yield* DatabaseServiceTag
553
586
  const agentConfig = yield* AgentConfigServiceTag
554
587
  const runtimeAdapters = yield* RuntimeAdaptersServiceTag
@@ -560,18 +593,27 @@ export const OwnershipDispatcherServiceLive = Layer.effect(
560
593
  const skillResolver = yield* SkillResolverServiceTag
561
594
  const systemExecutor = yield* SystemExecutorServiceTag
562
595
  const user = yield* UserServiceTag
596
+ const executeAgentNode = agentExecutor.executeNode as OwnershipDispatcherDeps['agentExecutor']['executeNode']
563
597
  return makeOwnershipDispatcherService({
564
598
  db,
565
599
  agentConfig,
566
600
  runtimeAdapters,
567
- agentExecutor,
568
- monitoringWindow,
601
+ agentExecutor: { executeNode: executeAgentNode },
602
+ monitoringWindow: {
603
+ startMonitoringWindow: (params) => provideCurrentContext(monitoringWindow.startMonitoringWindow(params)),
604
+ },
569
605
  planExecutor,
570
606
  planRun,
571
- pluginExecutor,
572
- skillResolver,
573
- systemExecutor,
574
- user,
607
+ pluginExecutor: {
608
+ validateOwner: (owner, nodeId) => pluginExecutor.validateOwner(owner, nodeId),
609
+ executeNode: (params) => provideCurrentContext(pluginExecutor.executeNode(params)),
610
+ },
611
+ skillResolver: { resolve: (params) => provideCurrentContext(skillResolver.resolve(params)) },
612
+ systemExecutor: {
613
+ validateOwner: (owner, nodeId) => systemExecutor.validateOwner(owner, nodeId),
614
+ executeNode: (params) => provideCurrentContext(systemExecutor.executeNode(params)),
615
+ },
616
+ user: { getUser: (userId) => provideCurrentContext(user.getUser(userId)) },
575
617
  })
576
618
  }),
577
619
  )
@@ -213,7 +213,7 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
213
213
  export class PlanAgentHeartbeatServiceTag extends Context.Service<
214
214
  PlanAgentHeartbeatServiceTag,
215
215
  ReturnType<typeof makePlanAgentHeartbeatService>
216
- >()('PlanAgentHeartbeatService') {}
216
+ >()('@lota-sdk/core/PlanAgentHeartbeatService') {}
217
217
 
218
218
  export const PlanAgentHeartbeatServiceLive = Layer.effect(
219
219
  PlanAgentHeartbeatServiceTag,
@@ -310,7 +310,7 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
310
310
  export class PlanAgentQueryServiceTag extends Context.Service<
311
311
  PlanAgentQueryServiceTag,
312
312
  ReturnType<typeof makePlanAgentQueryService>
313
- >()('PlanAgentQueryService') {}
313
+ >()('@lota-sdk/core/PlanAgentQueryService') {}
314
314
 
315
315
  export const PlanAgentQueryServiceLive = Layer.effect(
316
316
  PlanAgentQueryServiceTag,
@@ -12,48 +12,53 @@ import { nowDate } from '../../utils/date-time'
12
12
  import type { HumanNodeResponsePayload } from './plan-executor-helpers'
13
13
 
14
14
  export function makePlanApprovalService(db: SurrealDBService) {
15
- const createPendingApprovalEffect = (params: {
15
+ const createPendingApprovalEffect = Effect.fn('PlanApproval.createPendingApproval')(function* (params: {
16
16
  tx: DatabaseTransaction
17
17
  runId: RecordIdInput
18
18
  nodeRunId: RecordIdInput
19
19
  nodeId: string
20
20
  requestedBy: string
21
21
  presented: Record<string, unknown>
22
- }) =>
23
- Effect.gen(function* () {
24
- const approvalId = new RecordId(TABLES.PLAN_APPROVAL, Bun.randomUUIDv7())
25
- const created = yield* params.tx
26
- .create(approvalId)
27
- .content({
28
- runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
29
- nodeRunId: ensureRecordId(params.nodeRunId, TABLES.PLAN_NODE_RUN),
30
- nodeId: params.nodeId,
31
- status: 'pending',
32
- requestedBy: params.requestedBy,
33
- presented: params.presented,
34
- requiredEdits: [],
35
- })
36
- .output('after')
22
+ }) {
23
+ const approvalId = new RecordId(TABLES.PLAN_APPROVAL, Bun.randomUUIDv7())
24
+ const created = yield* params.tx
25
+ .create(approvalId)
26
+ .content({
27
+ runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
28
+ nodeRunId: ensureRecordId(params.nodeRunId, TABLES.PLAN_NODE_RUN),
29
+ nodeId: params.nodeId,
30
+ status: 'pending',
31
+ requestedBy: params.requestedBy,
32
+ presented: params.presented,
33
+ requiredEdits: [],
34
+ })
35
+ .output('after')
37
36
 
38
- return PlanApprovalSchema.parse(created)
39
- })
37
+ return PlanApprovalSchema.parse(created)
38
+ })
40
39
 
41
- const getApprovalByIdEffect = (approvalId: RecordIdInput) =>
42
- db.findOne(TABLES.PLAN_APPROVAL, { id: ensureRecordId(approvalId, TABLES.PLAN_APPROVAL) }, PlanApprovalSchema)
40
+ const getApprovalByIdEffect = Effect.fn('PlanApproval.getApprovalById')(function* (approvalId: RecordIdInput) {
41
+ return yield* db.findOne(
42
+ TABLES.PLAN_APPROVAL,
43
+ { id: ensureRecordId(approvalId, TABLES.PLAN_APPROVAL) },
44
+ PlanApprovalSchema,
45
+ )
46
+ })
43
47
 
44
- const getPendingApprovalForNodeRunEffect = (nodeRunId: RecordIdInput) =>
45
- Effect.gen(function* () {
46
- const approvals = yield* db.findMany(
47
- TABLES.PLAN_APPROVAL,
48
- { nodeRunId: ensureRecordId(nodeRunId, TABLES.PLAN_NODE_RUN), status: 'pending' },
49
- PlanApprovalSchema,
50
- { orderBy: 'createdAt', orderDir: 'DESC', limit: 1 },
51
- )
48
+ const getPendingApprovalForNodeRunEffect = Effect.fn('PlanApproval.getPendingApprovalForNodeRun')(function* (
49
+ nodeRunId: RecordIdInput,
50
+ ) {
51
+ const approvals = yield* db.findMany(
52
+ TABLES.PLAN_APPROVAL,
53
+ { nodeRunId: ensureRecordId(nodeRunId, TABLES.PLAN_NODE_RUN), status: 'pending' },
54
+ PlanApprovalSchema,
55
+ { orderBy: 'createdAt', orderDir: 'DESC', limit: 1 },
56
+ )
52
57
 
53
- return approvals.at(0) ?? null
54
- })
58
+ return approvals.at(0) ?? null
59
+ })
55
60
 
56
- const updateApprovalResponseEffect = (params: {
61
+ const updateApprovalResponseEffect = Effect.fn('PlanApproval.updateApprovalResponse')(function* (params: {
57
62
  tx: DatabaseTransaction
58
63
  approval: PlanApprovalRecord
59
64
  status: PlanApprovalStatus
@@ -62,23 +67,22 @@ export function makePlanApprovalService(db: SurrealDBService) {
62
67
  approvalMessageId?: string
63
68
  comments?: string
64
69
  requiredEdits?: string[]
65
- }) =>
66
- Effect.gen(function* () {
67
- const updated = yield* params.tx
68
- .update(ensureRecordId(params.approval.id, TABLES.PLAN_APPROVAL))
69
- .merge({
70
- status: params.status,
71
- response: params.response,
72
- respondedBy: params.respondedBy,
73
- ...(params.approvalMessageId ? { approvalMessageId: params.approvalMessageId } : {}),
74
- ...(params.comments ? { comments: params.comments } : {}),
75
- ...(params.requiredEdits ? { requiredEdits: params.requiredEdits } : {}),
76
- respondedAt: nowDate(),
77
- })
78
- .output('after')
70
+ }) {
71
+ const updated = yield* params.tx
72
+ .update(ensureRecordId(params.approval.id, TABLES.PLAN_APPROVAL))
73
+ .merge({
74
+ status: params.status,
75
+ response: params.response,
76
+ respondedBy: params.respondedBy,
77
+ ...(params.approvalMessageId ? { approvalMessageId: params.approvalMessageId } : {}),
78
+ ...(params.comments ? { comments: params.comments } : {}),
79
+ ...(params.requiredEdits ? { requiredEdits: params.requiredEdits } : {}),
80
+ respondedAt: nowDate(),
81
+ })
82
+ .output('after')
79
83
 
80
- return PlanApprovalSchema.parse(updated)
81
- })
84
+ return PlanApprovalSchema.parse(updated)
85
+ })
82
86
 
83
87
  return {
84
88
  createPendingApproval: createPendingApprovalEffect,
@@ -91,7 +95,7 @@ export function makePlanApprovalService(db: SurrealDBService) {
91
95
  export class PlanApprovalServiceTag extends Context.Service<
92
96
  PlanApprovalServiceTag,
93
97
  ReturnType<typeof makePlanApprovalService>
94
- >()('PlanApprovalService') {}
98
+ >()('@lota-sdk/core/PlanApprovalService') {}
95
99
 
96
100
  export const PlanApprovalServiceLive = Layer.effect(
97
101
  PlanApprovalServiceTag,
@@ -55,6 +55,6 @@ export function makePlanArtifactService() {
55
55
  export class PlanArtifactServiceTag extends Context.Service<
56
56
  PlanArtifactServiceTag,
57
57
  ReturnType<typeof makePlanArtifactService>
58
- >()('PlanArtifactService') {}
58
+ >()('@lota-sdk/core/PlanArtifactService') {}
59
59
 
60
- export const PlanArtifactServiceLive = Layer.succeed(PlanArtifactServiceTag, makePlanArtifactService())
60
+ export const PlanArtifactServiceLive = Layer.sync(PlanArtifactServiceTag, () => makePlanArtifactService())
@@ -71,6 +71,6 @@ export function makePlanBuilderService() {
71
71
  export class PlanBuilderServiceTag extends Context.Service<
72
72
  PlanBuilderServiceTag,
73
73
  ReturnType<typeof makePlanBuilderService>
74
- >()('PlanBuilderService') {}
74
+ >()('@lota-sdk/core/PlanBuilderService') {}
75
75
 
76
- export const PlanBuilderServiceLive = Layer.succeed(PlanBuilderServiceTag, makePlanBuilderService())
76
+ export const PlanBuilderServiceLive = Layer.sync(PlanBuilderServiceTag, () => makePlanBuilderService())
@@ -91,7 +91,7 @@ export function makePlanCheckpointService(deps: PlanCheckpointDeps) {
91
91
  export class PlanCheckpointServiceTag extends Context.Service<
92
92
  PlanCheckpointServiceTag,
93
93
  ReturnType<typeof makePlanCheckpointService>
94
- >()('PlanCheckpointService') {}
94
+ >()('@lota-sdk/core/PlanCheckpointService') {}
95
95
 
96
96
  export const PlanCheckpointServiceLive = Layer.effect(
97
97
  PlanCheckpointServiceTag,
@@ -95,7 +95,7 @@ export function makePlanCompilerService(planValidatorService: ReturnType<typeof
95
95
  export class PlanCompilerServiceTag extends Context.Service<
96
96
  PlanCompilerServiceTag,
97
97
  ReturnType<typeof makePlanCompilerService>
98
- >()('PlanCompilerService') {}
98
+ >()('@lota-sdk/core/PlanCompilerService') {}
99
99
 
100
100
  export const PlanCompilerServiceLive = Layer.effect(
101
101
  PlanCompilerServiceTag,
@@ -6,6 +6,7 @@ import { aiLogger } from '../../config/logger'
6
6
  import { ensureRecordId } from '../../db/record-id'
7
7
  import type { SurrealDBService } from '../../db/service'
8
8
  import { TABLES } from '../../db/tables'
9
+ import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
9
10
  import { runPromise } from '../../effect/runtime'
10
11
  import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
11
12
  import type { makeFeedbackLoopService } from '../feedback-loop.service'
@@ -33,25 +34,15 @@ class PlanCompletionSideEffectsError extends Schema.TaggedErrorClass<PlanComplet
33
34
  { message: Schema.String, cause: Schema.Defect },
34
35
  ) {}
35
36
 
37
+ const effectTryPlanCompletionPromise = makeEffectTryPromiseWithMessage(
38
+ (message, cause) => new PlanCompletionSideEffectsError({ message, cause }),
39
+ )
40
+
36
41
  function tryPlanCompletionPromise<A>(
37
42
  message: string,
38
- thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
43
+ evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
39
44
  ): Effect.Effect<A, PlanCompletionSideEffectsError> {
40
- return Effect.suspend(() => {
41
- try {
42
- const value = thunk()
43
- if (Effect.isEffect(value)) {
44
- return value.pipe(Effect.mapError((cause) => new PlanCompletionSideEffectsError({ message, cause })))
45
- }
46
-
47
- return Effect.tryPromise({
48
- try: () => Promise.resolve(value),
49
- catch: (cause) => new PlanCompletionSideEffectsError({ message, cause }),
50
- })
51
- } catch (cause) {
52
- return Effect.fail(new PlanCompletionSideEffectsError({ message, cause }))
53
- }
54
- })
45
+ return effectTryPlanCompletionPromise(evaluate, message)
55
46
  }
56
47
 
57
48
  export function makePlanCompletionSideEffects({
@@ -134,14 +125,17 @@ export function makePlanCompletionSideEffects({
134
125
  PlanEventSchema,
135
126
  ),
136
127
  )
137
- yield* Effect.tryPromise({
138
- try: () => planEventDeliveryService.dispatchEvent(event),
139
- catch: (cause) =>
140
- new PlanCompletionSideEffectsError({
141
- message: 'Failed to dispatch feedback analyzed plan event.',
142
- cause,
143
- }),
144
- })
128
+ yield* planEventDeliveryService
129
+ .dispatchEventEffect(event)
130
+ .pipe(
131
+ Effect.mapError(
132
+ (error) =>
133
+ new PlanCompletionSideEffectsError({
134
+ message: 'Failed to dispatch feedback analyzed plan event.',
135
+ cause: error,
136
+ }),
137
+ ),
138
+ )
145
139
  }
146
140
 
147
141
  yield* institutionalMemoryService
@@ -170,7 +170,7 @@ export function makePlanCoordinationService(planRunService: ReturnType<typeof ma
170
170
  export class PlanCoordinationServiceTag extends Context.Service<
171
171
  PlanCoordinationServiceTag,
172
172
  ReturnType<typeof makePlanCoordinationService>
173
- >()('PlanCoordinationService') {}
173
+ >()('@lota-sdk/core/PlanCoordinationService') {}
174
174
 
175
175
  export const PlanCoordinationServiceLive = Layer.effect(
176
176
  PlanCoordinationServiceTag,