@lota-sdk/core 0.4.12 → 0.4.14

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 (139) hide show
  1. package/package.json +4 -4
  2. package/src/ai/embedding-cache.ts +17 -11
  3. package/src/ai-gateway/ai-gateway.ts +164 -94
  4. package/src/ai-gateway/index.ts +4 -1
  5. package/src/config/agent-defaults.ts +2 -2
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/create-runtime.ts +259 -200
  8. package/src/db/cursor-pagination.ts +2 -9
  9. package/src/db/memory-store.ts +194 -175
  10. package/src/db/memory.ts +125 -71
  11. package/src/db/schema-fingerprint.ts +5 -4
  12. package/src/db/service-normalization.ts +4 -3
  13. package/src/db/service.ts +3 -2
  14. package/src/db/startup.ts +15 -16
  15. package/src/effect/errors.ts +161 -21
  16. package/src/effect/index.ts +0 -1
  17. package/src/embeddings/provider.ts +15 -7
  18. package/src/queues/autonomous-job.queue.ts +10 -22
  19. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  20. package/src/queues/document-processor.queue.ts +13 -4
  21. package/src/queues/memory-consolidation.queue.ts +26 -14
  22. package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
  23. package/src/queues/plan-scheduler.queue.ts +37 -15
  24. package/src/queues/queue-factory.ts +59 -35
  25. package/src/queues/standalone-worker.ts +3 -2
  26. package/src/redis/connection.ts +10 -3
  27. package/src/redis/org-memory-lock.ts +1 -1
  28. package/src/redis/redis-lease-lock.ts +5 -5
  29. package/src/redis/stream-context.ts +1 -1
  30. package/src/runtime/chat-message.ts +64 -1
  31. package/src/runtime/chat-run-orchestration.ts +33 -20
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  33. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  34. package/src/runtime/domain-layer.ts +13 -7
  35. package/src/runtime/execution-plan.ts +7 -3
  36. package/src/runtime/live-turn-trace.ts +6 -49
  37. package/src/runtime/memory/memory-block.ts +3 -9
  38. package/src/runtime/memory/memory-scope.ts +3 -1
  39. package/src/runtime/plugin-resolution.ts +2 -1
  40. package/src/runtime/post-turn-side-effects.ts +6 -5
  41. package/src/runtime/retrieval-adapters.ts +8 -20
  42. package/src/runtime/runtime-config.ts +3 -9
  43. package/src/runtime/runtime-extensions.ts +2 -4
  44. package/src/runtime/runtime-lifecycle.ts +56 -16
  45. package/src/runtime/runtime-services.ts +180 -102
  46. package/src/runtime/runtime-worker-registry.ts +3 -1
  47. package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
  48. package/src/runtime/social-chat/social-chat-history.ts +21 -18
  49. package/src/runtime/social-chat/social-chat.ts +356 -223
  50. package/src/runtime/specialist-runner.ts +3 -1
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
  52. package/src/runtime/thread-turn-context.ts +142 -102
  53. package/src/runtime/turn-lifecycle.ts +15 -46
  54. package/src/services/agent-activity.service.ts +1 -1
  55. package/src/services/agent-executor.service.ts +107 -77
  56. package/src/services/autonomous-job.service.ts +354 -293
  57. package/src/services/background-work.service.ts +3 -3
  58. package/src/services/context-compaction.service.ts +7 -2
  59. package/src/services/document-chunk.service.ts +50 -32
  60. package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
  61. package/src/services/execution-plan/execution-plan.service.ts +162 -179
  62. package/src/services/feedback-loop.service.ts +5 -4
  63. package/src/services/graph-full-routing.ts +37 -36
  64. package/src/services/institutional-memory.service.ts +28 -30
  65. package/src/services/learned-skill.service.ts +107 -72
  66. package/src/services/memory/memory-errors.ts +4 -23
  67. package/src/services/memory/memory-org-memory.ts +10 -5
  68. package/src/services/memory/memory-rerank.ts +18 -6
  69. package/src/services/memory/memory.service.ts +170 -111
  70. package/src/services/memory/rerank.service.ts +29 -20
  71. package/src/services/organization-member.service.ts +1 -1
  72. package/src/services/organization.service.ts +69 -75
  73. package/src/services/ownership-dispatcher.service.ts +40 -39
  74. package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
  75. package/src/services/plan/plan-agent-query.service.ts +39 -31
  76. package/src/services/plan/plan-completion-side-effects.ts +13 -17
  77. package/src/services/plan/plan-coordination.service.ts +2 -1
  78. package/src/services/plan/plan-cycle.service.ts +6 -5
  79. package/src/services/plan/plan-deadline.service.ts +57 -54
  80. package/src/services/plan/plan-event-delivery.service.ts +5 -4
  81. package/src/services/plan/plan-executor-graph.ts +18 -15
  82. package/src/services/plan/plan-executor.service.ts +235 -262
  83. package/src/services/plan/plan-run.service.ts +169 -93
  84. package/src/services/plan/plan-scheduler.service.ts +192 -202
  85. package/src/services/plan/plan-template.service.ts +1 -1
  86. package/src/services/plan/plan-transaction-events.ts +1 -1
  87. package/src/services/plan/plan-workspace.service.ts +23 -14
  88. package/src/services/plugin-executor.service.ts +5 -9
  89. package/src/services/queue-job.service.ts +117 -59
  90. package/src/services/recent-activity-title.service.ts +13 -12
  91. package/src/services/recent-activity.service.ts +6 -1
  92. package/src/services/social-chat-history.service.ts +29 -25
  93. package/src/services/system-executor.service.ts +5 -9
  94. package/src/services/thread/thread-active-run.ts +2 -2
  95. package/src/services/thread/thread-listing.ts +61 -57
  96. package/src/services/thread/thread-memory-block.ts +73 -48
  97. package/src/services/thread/thread-message.service.ts +76 -65
  98. package/src/services/thread/thread-record-store.ts +8 -8
  99. package/src/services/thread/thread-title.service.ts +10 -4
  100. package/src/services/thread/thread-turn-execution.ts +43 -45
  101. package/src/services/thread/thread-turn-preparation.service.ts +257 -135
  102. package/src/services/thread/thread-turn-streaming.ts +82 -85
  103. package/src/services/thread/thread-turn.ts +8 -8
  104. package/src/services/thread/thread.service.ts +135 -100
  105. package/src/services/user.service.ts +45 -48
  106. package/src/storage/attachment-parser.ts +6 -2
  107. package/src/storage/attachment-storage.service.ts +5 -6
  108. package/src/storage/generated-document-storage.service.ts +1 -1
  109. package/src/system-agents/context-compaction.agent.ts +10 -9
  110. package/src/system-agents/delegated-agent-factory.ts +30 -6
  111. package/src/system-agents/memory-reranker.agent.ts +10 -9
  112. package/src/system-agents/memory.agent.ts +10 -9
  113. package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
  114. package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
  115. package/src/system-agents/skill-extractor.agent.ts +13 -12
  116. package/src/system-agents/skill-manager.agent.ts +13 -12
  117. package/src/system-agents/thread-router.agent.ts +10 -5
  118. package/src/system-agents/title-generator.agent.ts +13 -12
  119. package/src/tools/fetch-webpage.tool.ts +13 -13
  120. package/src/tools/memory-block.tool.ts +3 -1
  121. package/src/tools/plan-approval.tool.ts +4 -2
  122. package/src/tools/read-file-parts.tool.ts +10 -4
  123. package/src/tools/remember-memory.tool.ts +3 -1
  124. package/src/tools/research-topic.tool.ts +9 -5
  125. package/src/tools/search-web.tool.ts +16 -16
  126. package/src/tools/search.tool.ts +20 -5
  127. package/src/tools/team-think.tool.ts +61 -38
  128. package/src/utils/async.ts +5 -5
  129. package/src/utils/errors.ts +19 -18
  130. package/src/utils/sse-keepalive.ts +28 -25
  131. package/src/workers/bootstrap.ts +75 -11
  132. package/src/workers/memory-consolidation.worker.ts +82 -91
  133. package/src/workers/organization-learning.worker.ts +14 -4
  134. package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
  135. package/src/workers/skill-extraction.runner.ts +97 -61
  136. package/src/workers/utils/repo-structure-extractor.ts +13 -8
  137. package/src/workers/utils/thread-message-query.ts +24 -24
  138. package/src/workers/worker-utils.ts +23 -4
  139. package/src/effect/helpers.ts +0 -123
@@ -8,8 +8,7 @@ import type { RecordIdInput } from '../../db/record-id'
8
8
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
9
9
  import type { DatabaseTransaction } from '../../db/service'
10
10
  import { TABLES } from '../../db/tables'
11
- import { BadRequestError, NotFoundError } from '../../effect/errors'
12
- import { effectTryPromise as effectTryPromiseShared } from '../../effect/helpers'
11
+ import { ERROR_TAGS, BadRequestError, NotFoundError } from '../../effect/errors'
13
12
  import { DatabaseServiceTag } from '../../effect/services'
14
13
  import type { DelayedNodePromotionQueueRuntime } from '../../queues/delayed-node-promotion.queue'
15
14
  import { LotaQueuesServiceTag } from '../../queues/queues.service'
@@ -17,7 +16,7 @@ import { GeneratedDocumentStorageServiceTag } from '../../storage/generated-docu
17
16
  import { nowDate } from '../../utils/date-time'
18
17
  import { toError } from '../../utils/errors'
19
18
  import { ArtifactServiceTag } from '../artifact.service'
20
- import { BackgroundWorkService } from '../background-work.service'
19
+ import { BackgroundWorkServiceTag } from '../background-work.service'
21
20
  import { FeedbackLoopServiceTag } from '../feedback-loop.service'
22
21
  import { InstitutionalMemoryServiceTag } from '../institutional-memory.service'
23
22
  import { QualityMetricsServiceTag } from '../quality-metrics.service'
@@ -85,15 +84,12 @@ function saveCheckpointWithContext(
85
84
  }
86
85
 
87
86
  class PlanExecutorInternalError extends Schema.TaggedErrorClass<PlanExecutorInternalError>()(
88
- 'PlanExecutorInternalError',
87
+ '@lota-sdk/core/PlanExecutorInternalError',
89
88
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
90
89
  ) {}
91
90
 
92
- function fromPromise<A>(thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>) {
93
- return effectTryPromiseShared(
94
- thunk,
95
- (cause) => new PlanExecutorInternalError({ message: toError(cause).message, cause }),
96
- )
91
+ function toPlanExecutorInternalError(cause: unknown): PlanExecutorInternalError {
92
+ return new PlanExecutorInternalError({ message: toError(cause).message, cause })
97
93
  }
98
94
 
99
95
  function parseRowOrFail<T>(
@@ -320,25 +316,24 @@ function persistNodeResultAttemptEffect(
320
316
  issues: [...submission.validation.blocking, ...submission.validation.warnings],
321
317
  })
322
318
 
323
- const updatedAttemptRow = yield* fromPromise(() =>
324
- tx
325
- .update(ensureRecordId(attempt.id, TABLES.PLAN_NODE_ATTEMPT))
326
- .content({
327
- runId: ensureRecordId(attempt.runId, TABLES.PLAN_RUN),
328
- nodeRunId: ensureRecordId(attempt.nodeRunId, TABLES.PLAN_NODE_RUN),
329
- nodeId: attempt.nodeId,
330
- emittedBy: attempt.emittedBy,
331
- status: attempt.status,
332
- ...(attempt.structuredOutput ? { structuredOutput: attempt.structuredOutput } : {}),
333
- ...(attempt.notes ? { notes: attempt.notes } : {}),
334
- validationIssueIds: issues.map((issue: { id: RecordIdInput }) =>
335
- ensureRecordId(issue.id, TABLES.PLAN_VALIDATION_ISSUE),
336
- ),
337
- ...(attempt.failureClass ? { failureClass: attempt.failureClass } : {}),
338
- })
339
- .output('after'),
340
- )
341
- void (yield* parseRowOrFail(PlanNodeAttemptSchema, updatedAttemptRow, 'plan node attempt'))
319
+ const updatedAttemptRow = yield* tx
320
+ .update(ensureRecordId(attempt.id, TABLES.PLAN_NODE_ATTEMPT))
321
+ .content({
322
+ runId: ensureRecordId(attempt.runId, TABLES.PLAN_RUN),
323
+ nodeRunId: ensureRecordId(attempt.nodeRunId, TABLES.PLAN_NODE_RUN),
324
+ nodeId: attempt.nodeId,
325
+ emittedBy: attempt.emittedBy,
326
+ status: attempt.status,
327
+ ...(attempt.structuredOutput ? { structuredOutput: attempt.structuredOutput } : {}),
328
+ ...(attempt.notes ? { notes: attempt.notes } : {}),
329
+ validationIssueIds: issues.map((issue: { id: RecordIdInput }) =>
330
+ ensureRecordId(issue.id, TABLES.PLAN_VALIDATION_ISSUE),
331
+ ),
332
+ ...(attempt.failureClass ? { failureClass: attempt.failureClass } : {}),
333
+ })
334
+ .output('after')
335
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
336
+ yield* Effect.asVoid(parseRowOrFail(PlanNodeAttemptSchema, updatedAttemptRow, 'plan node attempt'))
342
337
 
343
338
  const publishedArtifacts =
344
339
  submission.validation.blocking.length > 0
@@ -362,19 +357,18 @@ function persistNodeResultAttemptEffect(
362
357
  artifacts: publishedArtifacts,
363
358
  })
364
359
 
365
- const nextNodeRunRow = yield* fromPromise(() =>
366
- tx
367
- .update(ensureRecordId(submission.nodeRun.id, TABLES.PLAN_NODE_RUN))
368
- .content(
369
- toNodeRunData(submission.nodeRun, {
370
- attemptCount: submission.nodeRun.attemptCount + 1,
371
- latestAttemptId: attempt.id,
372
- latestStructuredOutput: result.structuredOutput ?? null,
373
- latestNotes: result.notes,
374
- }),
375
- )
376
- .output('after'),
377
- )
360
+ const nextNodeRunRow = yield* tx
361
+ .update(ensureRecordId(submission.nodeRun.id, TABLES.PLAN_NODE_RUN))
362
+ .content(
363
+ toNodeRunData(submission.nodeRun, {
364
+ attemptCount: submission.nodeRun.attemptCount + 1,
365
+ latestAttemptId: attempt.id,
366
+ latestStructuredOutput: result.structuredOutput ?? null,
367
+ latestNotes: result.notes,
368
+ }),
369
+ )
370
+ .output('after')
371
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
378
372
  const nextNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, nextNodeRunRow, 'plan node run')
379
373
 
380
374
  const nodeRuns = yield* context.planRunService.listNodeRuns(submission.run.id)
@@ -401,22 +395,21 @@ function handleRetryNodeResultEffect(
401
395
  const { tx, emittedEvents, submission, persisted, emittedBy } = params
402
396
 
403
397
  return Effect.gen(function* () {
404
- const retryNodeRunRow = yield* fromPromise(() =>
405
- tx
406
- .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
407
- .content(
408
- toNodeRunData(persisted.nextNodeRun, {
409
- status: 'ready',
410
- retryCount: persisted.nextNodeRun.retryCount + 1,
411
- failureClass: submission.validation.failureClass,
412
- blockedReason: submission.validation.blocking[0]?.message ?? null,
413
- readyAt: nowDate(),
414
- startedAt: null,
415
- completedAt: null,
416
- }),
417
- )
418
- .output('after'),
419
- )
398
+ const retryNodeRunRow = yield* tx
399
+ .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
400
+ .content(
401
+ toNodeRunData(persisted.nextNodeRun, {
402
+ status: 'ready',
403
+ retryCount: persisted.nextNodeRun.retryCount + 1,
404
+ failureClass: submission.validation.failureClass,
405
+ blockedReason: submission.validation.blocking[0]?.message ?? null,
406
+ readyAt: nowDate(),
407
+ startedAt: null,
408
+ completedAt: null,
409
+ }),
410
+ )
411
+ .output('after')
412
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
420
413
  const retryNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, retryNodeRunRow, 'plan node run')
421
414
 
422
415
  yield* emitEvent({
@@ -447,18 +440,16 @@ function handleRetryNodeResultEffect(
447
440
  capturedEvents: emittedEvents,
448
441
  })
449
442
 
450
- const synced = yield* fromPromise(() =>
451
- syncRunGraph(context, {
452
- tx,
453
- run: submission.run,
454
- spec: submission.spec,
455
- nodeSpecs: submission.nodeSpecs,
456
- nodeRuns: replaceNodeRunInList(persisted.withUpdatedNodeRuns, retryNodeRun),
457
- artifacts: persisted.nextArtifacts,
458
- emittedBy,
459
- capturedEvents: emittedEvents,
460
- }),
461
- )
443
+ const synced = yield* syncRunGraph(context, {
444
+ tx,
445
+ run: submission.run,
446
+ spec: submission.spec,
447
+ nodeSpecs: submission.nodeSpecs,
448
+ nodeRuns: replaceNodeRunInList(persisted.withUpdatedNodeRuns, retryNodeRun),
449
+ artifacts: persisted.nextArtifacts,
450
+ emittedBy,
451
+ capturedEvents: emittedEvents,
452
+ }).pipe(Effect.mapError(toPlanExecutorInternalError))
462
453
 
463
454
  yield* saveAndAttachCheckpointEffect(context, {
464
455
  tx,
@@ -486,20 +477,19 @@ function handleHumanReviewNodeResultEffect(
486
477
  const { tx, emittedEvents, submission, persisted, emittedBy } = params
487
478
 
488
479
  return Effect.gen(function* () {
489
- const awaitingHumanNodeRunRow = yield* fromPromise(() =>
490
- tx
491
- .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
492
- .content(
493
- toNodeRunData(persisted.nextNodeRun, {
494
- status: 'awaiting-human',
495
- retryCount: persisted.nextNodeRun.retryCount + 1,
496
- failureClass: submission.validation.failureClass,
497
- blockedReason: submission.validation.blocking[0]?.message ?? null,
498
- startedAt: persisted.nextNodeRun.startedAt ?? nowDate(),
499
- }),
500
- )
501
- .output('after'),
502
- )
480
+ const awaitingHumanNodeRunRow = yield* tx
481
+ .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
482
+ .content(
483
+ toNodeRunData(persisted.nextNodeRun, {
484
+ status: 'awaiting-human',
485
+ retryCount: persisted.nextNodeRun.retryCount + 1,
486
+ failureClass: submission.validation.failureClass,
487
+ blockedReason: submission.validation.blocking[0]?.message ?? null,
488
+ startedAt: persisted.nextNodeRun.startedAt ?? nowDate(),
489
+ }),
490
+ )
491
+ .output('after')
492
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
503
493
  const awaitingHumanNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, awaitingHumanNodeRunRow, 'plan node run')
504
494
 
505
495
  const approval = yield* context.planApprovalService
@@ -517,7 +507,7 @@ function handleHumanReviewNodeResultEffect(
517
507
  validationIssues: submission.validation.blocking,
518
508
  },
519
509
  })
520
- .pipe(Effect.mapError((cause) => new PlanExecutorInternalError({ message: toError(cause).message, cause })))
510
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
521
511
 
522
512
  const awaitingHumanRun = yield* replaceRun(tx, submission.run, {
523
513
  status: 'awaiting-human',
@@ -569,19 +559,18 @@ function handleReplanNodeResultEffect(
569
559
  const { tx, emittedEvents, submission, persisted, emittedBy } = params
570
560
 
571
561
  return Effect.gen(function* () {
572
- const blockedNodeRunRow = yield* fromPromise(() =>
573
- tx
574
- .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
575
- .content(
576
- toNodeRunData(persisted.nextNodeRun, {
577
- status: 'blocked',
578
- retryCount: persisted.nextNodeRun.retryCount + 1,
579
- failureClass: submission.validation.failureClass,
580
- blockedReason: submission.validation.blocking[0]?.message ?? null,
581
- }),
582
- )
583
- .output('after'),
584
- )
562
+ const blockedNodeRunRow = yield* tx
563
+ .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
564
+ .content(
565
+ toNodeRunData(persisted.nextNodeRun, {
566
+ status: 'blocked',
567
+ retryCount: persisted.nextNodeRun.retryCount + 1,
568
+ failureClass: submission.validation.failureClass,
569
+ blockedReason: submission.validation.blocking[0]?.message ?? null,
570
+ }),
571
+ )
572
+ .output('after')
573
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
585
574
  const blockedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, blockedNodeRunRow, 'plan node run')
586
575
 
587
576
  const blockedRun = yield* replaceRun(tx, submission.run, {
@@ -636,20 +625,19 @@ function handleFailedNodeResultEffect(
636
625
  const { tx, emittedEvents, submission, persisted, emittedBy } = params
637
626
 
638
627
  return Effect.gen(function* () {
639
- const failedNodeRunRow = yield* fromPromise(() =>
640
- tx
641
- .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
642
- .content(
643
- toNodeRunData(persisted.nextNodeRun, {
644
- status: 'failed',
645
- retryCount: persisted.nextNodeRun.retryCount + 1,
646
- failureClass: submission.validation.failureClass,
647
- blockedReason: submission.validation.blocking[0]?.message ?? null,
648
- completedAt: nowDate(),
649
- }),
650
- )
651
- .output('after'),
652
- )
628
+ const failedNodeRunRow = yield* tx
629
+ .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
630
+ .content(
631
+ toNodeRunData(persisted.nextNodeRun, {
632
+ status: 'failed',
633
+ retryCount: persisted.nextNodeRun.retryCount + 1,
634
+ failureClass: submission.validation.failureClass,
635
+ blockedReason: submission.validation.blocking[0]?.message ?? null,
636
+ completedAt: nowDate(),
637
+ }),
638
+ )
639
+ .output('after')
640
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
653
641
  const failedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, failedNodeRunRow, 'plan node run')
654
642
 
655
643
  const failedRun = yield* replaceRun(tx, submission.run, {
@@ -741,22 +729,21 @@ function handleSuccessfulNodeResultEffect(
741
729
  const { tx, emittedEvents, submission, persisted, emittedBy, result } = params
742
730
 
743
731
  return Effect.gen(function* () {
744
- const completedNodeRunRow = yield* fromPromise(() =>
745
- tx
746
- .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
747
- .content(
748
- toNodeRunData(persisted.nextNodeRun, {
749
- status: submission.validation.warnings.length > 0 ? 'partial' : 'completed',
750
- latestAttemptId: persisted.attemptId,
751
- latestStructuredOutput: result.structuredOutput ?? null,
752
- latestNotes: result.notes,
753
- blockedReason: null,
754
- failureClass: null,
755
- completedAt: nowDate(),
756
- }),
757
- )
758
- .output('after'),
759
- )
732
+ const completedNodeRunRow = yield* tx
733
+ .update(ensureRecordId(persisted.nextNodeRun.id, TABLES.PLAN_NODE_RUN))
734
+ .content(
735
+ toNodeRunData(persisted.nextNodeRun, {
736
+ status: submission.validation.warnings.length > 0 ? 'partial' : 'completed',
737
+ latestAttemptId: persisted.attemptId,
738
+ latestStructuredOutput: result.structuredOutput ?? null,
739
+ latestNotes: result.notes,
740
+ blockedReason: null,
741
+ failureClass: null,
742
+ completedAt: nowDate(),
743
+ }),
744
+ )
745
+ .output('after')
746
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
760
747
  const completedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, completedNodeRunRow, 'plan node run')
761
748
 
762
749
  yield* emitEvent({
@@ -777,18 +764,16 @@ function handleSuccessfulNodeResultEffect(
777
764
  capturedEvents: emittedEvents,
778
765
  })
779
766
 
780
- const synced = yield* fromPromise(() =>
781
- syncRunGraph(context, {
782
- tx,
783
- run: submission.run,
784
- spec: submission.spec,
785
- nodeSpecs: submission.nodeSpecs,
786
- nodeRuns: replaceNodeRunInList(persisted.withUpdatedNodeRuns, completedNodeRun),
787
- artifacts: persisted.nextArtifacts,
788
- emittedBy,
789
- capturedEvents: emittedEvents,
790
- }),
791
- )
767
+ const synced = yield* syncRunGraph(context, {
768
+ tx,
769
+ run: submission.run,
770
+ spec: submission.spec,
771
+ nodeSpecs: submission.nodeSpecs,
772
+ nodeRuns: replaceNodeRunInList(persisted.withUpdatedNodeRuns, completedNodeRun),
773
+ artifacts: persisted.nextArtifacts,
774
+ emittedBy,
775
+ capturedEvents: emittedEvents,
776
+ }).pipe(Effect.mapError(toPlanExecutorInternalError))
792
777
 
793
778
  yield* saveAndAttachCheckpointEffect(context, {
794
779
  tx,
@@ -954,43 +939,41 @@ function persistHumanNodeResponseAttemptEffect(
954
939
  issues: [...submission.validation.blocking, ...submission.validation.warnings],
955
940
  })
956
941
 
957
- const updatedHumanAttemptRow = yield* fromPromise(() =>
958
- tx
959
- .update(ensureRecordId(attempt.id, TABLES.PLAN_NODE_ATTEMPT))
960
- .content({
961
- runId: ensureRecordId(attempt.runId, TABLES.PLAN_RUN),
962
- nodeRunId: ensureRecordId(attempt.nodeRunId, TABLES.PLAN_NODE_RUN),
963
- nodeId: attempt.nodeId,
964
- emittedBy: attempt.emittedBy,
965
- status: attempt.status,
966
- structuredOutput: response,
967
- validationIssueIds: issues.map((issue: { id: RecordIdInput }) =>
968
- ensureRecordId(issue.id, TABLES.PLAN_VALIDATION_ISSUE),
969
- ),
970
- ...(attempt.notes ? { notes: attempt.notes } : {}),
971
- ...(attempt.failureClass ? { failureClass: attempt.failureClass } : {}),
972
- })
973
- .output('after'),
974
- )
975
- void (yield* parseRowOrFail(PlanNodeAttemptSchema, updatedHumanAttemptRow, 'plan node attempt'))
976
-
977
- const nextNodeRunRow = yield* fromPromise(() =>
978
- tx
979
- .update(ensureRecordId(submission.nodeRun.id, TABLES.PLAN_NODE_RUN))
980
- .content(
981
- toNodeRunData(submission.nodeRun, {
982
- status: submission.validation.blocking.length > 0 ? 'blocked' : 'completed',
983
- attemptCount: submission.nodeRun.attemptCount + 1,
984
- latestAttemptId: attempt.id,
985
- latestStructuredOutput: response,
986
- latestNotes: submission.responseComments ?? null,
987
- blockedReason: submission.validation.blocking[0]?.message ?? null,
988
- failureClass: submission.validation.blocking.length > 0 ? submission.validation.failureClass : null,
989
- ...(submission.validation.blocking.length > 0 ? {} : { completedAt: nowDate() }),
990
- }),
991
- )
992
- .output('after'),
993
- )
942
+ const updatedHumanAttemptRow = yield* tx
943
+ .update(ensureRecordId(attempt.id, TABLES.PLAN_NODE_ATTEMPT))
944
+ .content({
945
+ runId: ensureRecordId(attempt.runId, TABLES.PLAN_RUN),
946
+ nodeRunId: ensureRecordId(attempt.nodeRunId, TABLES.PLAN_NODE_RUN),
947
+ nodeId: attempt.nodeId,
948
+ emittedBy: attempt.emittedBy,
949
+ status: attempt.status,
950
+ structuredOutput: response,
951
+ validationIssueIds: issues.map((issue: { id: RecordIdInput }) =>
952
+ ensureRecordId(issue.id, TABLES.PLAN_VALIDATION_ISSUE),
953
+ ),
954
+ ...(attempt.notes ? { notes: attempt.notes } : {}),
955
+ ...(attempt.failureClass ? { failureClass: attempt.failureClass } : {}),
956
+ })
957
+ .output('after')
958
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
959
+ yield* Effect.asVoid(parseRowOrFail(PlanNodeAttemptSchema, updatedHumanAttemptRow, 'plan node attempt'))
960
+
961
+ const nextNodeRunRow = yield* tx
962
+ .update(ensureRecordId(submission.nodeRun.id, TABLES.PLAN_NODE_RUN))
963
+ .content(
964
+ toNodeRunData(submission.nodeRun, {
965
+ status: submission.validation.blocking.length > 0 ? 'blocked' : 'completed',
966
+ attemptCount: submission.nodeRun.attemptCount + 1,
967
+ latestAttemptId: attempt.id,
968
+ latestStructuredOutput: response,
969
+ latestNotes: submission.responseComments ?? null,
970
+ blockedReason: submission.validation.blocking[0]?.message ?? null,
971
+ failureClass: submission.validation.blocking.length > 0 ? submission.validation.failureClass : null,
972
+ ...(submission.validation.blocking.length > 0 ? {} : { completedAt: nowDate() }),
973
+ }),
974
+ )
975
+ .output('after')
976
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
994
977
  const nextNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, nextNodeRunRow, 'plan node run')
995
978
 
996
979
  const nodeRuns = replaceNodeRunInList(yield* context.planRunService.listNodeRuns(submission.run.id), nextNodeRun)
@@ -1065,18 +1048,16 @@ function handleAcceptedHumanNodeResponseEffect(
1065
1048
  const { tx, emittedEvents, submission, persisted, respondedBy } = params
1066
1049
 
1067
1050
  return Effect.gen(function* () {
1068
- const synced = yield* fromPromise(() =>
1069
- syncRunGraph(context, {
1070
- tx,
1071
- run: submission.run,
1072
- spec: submission.spec,
1073
- nodeSpecs: submission.nodeSpecs,
1074
- nodeRuns: persisted.nodeRuns,
1075
- artifacts: submission.existingArtifacts,
1076
- emittedBy: respondedBy,
1077
- capturedEvents: emittedEvents,
1078
- }),
1079
- )
1051
+ const synced = yield* syncRunGraph(context, {
1052
+ tx,
1053
+ run: submission.run,
1054
+ spec: submission.spec,
1055
+ nodeSpecs: submission.nodeSpecs,
1056
+ nodeRuns: persisted.nodeRuns,
1057
+ artifacts: submission.existingArtifacts,
1058
+ emittedBy: respondedBy,
1059
+ capturedEvents: emittedEvents,
1060
+ }).pipe(Effect.mapError(toPlanExecutorInternalError))
1080
1061
 
1081
1062
  yield* emitEvent({
1082
1063
  tx,
@@ -1176,11 +1157,11 @@ const submitNodeResult = Effect.fn('PlanExecutor.submitNodeResult')(function* (
1176
1157
  const orgId = recordIdToString(submission.run.organizationId, TABLES.ORGANIZATION)
1177
1158
  const runIdStr = recordIdToString(submission.run.id, TABLES.PLAN_RUN)
1178
1159
 
1179
- const background = yield* BackgroundWorkService
1160
+ const background = yield* BackgroundWorkServiceTag
1180
1161
 
1181
1162
  yield* background.run(
1182
- fromPromise(() =>
1183
- planCompletionSideEffects.runPlanNodeCompletionSideEffects({
1163
+ planCompletionSideEffects
1164
+ .runPlanNodeCompletionSideEffects({
1184
1165
  runId: runIdStr,
1185
1166
  organizationId: orgId,
1186
1167
  nodeId: params.nodeId,
@@ -1192,24 +1173,24 @@ const submitNodeResult = Effect.fn('PlanExecutor.submitNodeResult')(function* (
1192
1173
  nodeAttemptCount: submission.nodeRun.attemptCount + 1,
1193
1174
  artifactCount: params.result.artifacts.length,
1194
1175
  validationIssues: [...submission.validation.blocking, ...submission.validation.warnings],
1195
- }),
1196
- ).pipe(
1197
- Effect.catchTag('PlanExecutorInternalError', (error) =>
1198
- Effect.sync(() => {
1199
- aiLogger.warn`Failed to record node completion metrics for run ${runIdStr} node ${params.nodeId}: ${error.message}`
1200
- }),
1176
+ })
1177
+ .pipe(
1178
+ Effect.mapError(toPlanExecutorInternalError),
1179
+ Effect.catchTag(ERROR_TAGS.PlanExecutorInternalError, (error) =>
1180
+ Effect.sync(() => {
1181
+ aiLogger.warn`Failed to record node completion metrics for run ${runIdStr} node ${params.nodeId}: ${error.message}`
1182
+ }),
1183
+ ),
1201
1184
  ),
1202
- ),
1203
1185
  'plan-executor.recordNodeCompletionMetrics',
1204
1186
  )
1205
1187
 
1206
1188
  const updatedRun = yield* planRunService.getRunById(submission.run.id)
1207
1189
  if (updatedRun.status === 'completed') {
1208
1190
  yield* background.run(
1209
- fromPromise(() =>
1210
- planCompletionSideEffects.runPlanCompletionSideEffectsSafely({ runId: runIdStr, organizationId: orgId }),
1211
- ).pipe(
1212
- Effect.catchTag('PlanExecutorInternalError', (error) =>
1191
+ planCompletionSideEffects.runPlanCompletionSideEffectsSafely({ runId: runIdStr, organizationId: orgId }).pipe(
1192
+ Effect.mapError(toPlanExecutorInternalError),
1193
+ Effect.catchTag(ERROR_TAGS.PlanExecutorInternalError, (error) =>
1213
1194
  Effect.sync(() => {
1214
1195
  aiLogger.warn`Plan completion side-effects failed for run ${runIdStr}: ${error.message}`
1215
1196
  }),
@@ -1302,18 +1283,17 @@ const resumeRun = Effect.fn('PlanExecutor.resumeRun')(function* (
1302
1283
  Effect.gen(function* () {
1303
1284
  let currentNodeRuns = [...nodeRuns]
1304
1285
  for (const currentNodeRun of currentNodeRuns.filter((candidate) => candidate.status === 'running')) {
1305
- const resetNodeRunRow = yield* fromPromise(() =>
1306
- tx
1307
- .update(ensureRecordId(currentNodeRun.id, TABLES.PLAN_NODE_RUN))
1308
- .content(
1309
- toNodeRunData(currentNodeRun, {
1310
- status: 'ready',
1311
- readyAt: nowDate(),
1312
- startedAt: currentNodeRun.startedAt ?? nowDate(),
1313
- }),
1314
- )
1315
- .output('after'),
1316
- )
1286
+ const resetNodeRunRow = yield* tx
1287
+ .update(ensureRecordId(currentNodeRun.id, TABLES.PLAN_NODE_RUN))
1288
+ .content(
1289
+ toNodeRunData(currentNodeRun, {
1290
+ status: 'ready',
1291
+ readyAt: nowDate(),
1292
+ startedAt: currentNodeRun.startedAt ?? nowDate(),
1293
+ }),
1294
+ )
1295
+ .output('after')
1296
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
1317
1297
  const resetNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, resetNodeRunRow, 'plan node run')
1318
1298
  currentNodeRuns = currentNodeRuns.map((candidate) =>
1319
1299
  candidate.nodeId === resetNodeRun.nodeId ? resetNodeRun : candidate,
@@ -1347,18 +1327,16 @@ const resumeRun = Effect.fn('PlanExecutor.resumeRun')(function* (
1347
1327
  const synced =
1348
1328
  resetRun.status === 'awaiting-human'
1349
1329
  ? { run: resetRun, nodeRuns: currentNodeRuns, artifacts }
1350
- : yield* fromPromise(() =>
1351
- syncRunGraph(context, {
1352
- tx,
1353
- run: resetRun,
1354
- spec,
1355
- nodeSpecs,
1356
- nodeRuns: currentNodeRuns,
1357
- artifacts,
1358
- emittedBy: params.emittedBy,
1359
- capturedEvents: emittedEvents,
1360
- }),
1361
- )
1330
+ : yield* syncRunGraph(context, {
1331
+ tx,
1332
+ run: resetRun,
1333
+ spec,
1334
+ nodeSpecs,
1335
+ nodeRuns: currentNodeRuns,
1336
+ artifacts,
1337
+ emittedBy: params.emittedBy,
1338
+ capturedEvents: emittedEvents,
1339
+ }).pipe(Effect.mapError(toPlanExecutorInternalError))
1362
1340
 
1363
1341
  const checkpoint = yield* saveCheckpointWithContext(context, {
1364
1342
  tx,
@@ -1395,12 +1373,11 @@ const transitionNodeToRunning = Effect.fn('PlanExecutor.transitionNodeToRunning'
1395
1373
 
1396
1374
  yield* withDatabaseTransactionEffect(databaseService, (tx) =>
1397
1375
  Effect.gen(function* () {
1398
- const runningNodeRunRow = yield* fromPromise(() =>
1399
- tx
1400
- .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
1401
- .content(toNodeRunData(nodeRun, { status: 'running', startedAt: nodeRun.startedAt ?? nowDate() }))
1402
- .output('after'),
1403
- )
1376
+ const runningNodeRunRow = yield* tx
1377
+ .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
1378
+ .content(toNodeRunData(nodeRun, { status: 'running', startedAt: nodeRun.startedAt ?? nowDate() }))
1379
+ .output('after')
1380
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
1404
1381
  const runningNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, runningNodeRunRow, 'plan node run')
1405
1382
 
1406
1383
  const nodeRuns = yield* planRunService.listNodeRuns(run.id)
@@ -1447,18 +1424,17 @@ const blockNodeOnDispatchFailure = Effect.fn('PlanExecutor.blockNodeOnDispatchFa
1447
1424
  if (nodeRun.status === 'blocked') {
1448
1425
  blockedNodeRun = nodeRun
1449
1426
  } else {
1450
- const blockedNodeRunRow = yield* fromPromise(() =>
1451
- tx
1452
- .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
1453
- .content(
1454
- toNodeRunData(nodeRun, {
1455
- status: 'blocked',
1456
- blockedReason: params.message,
1457
- failureClass: params.failureClass,
1458
- }),
1459
- )
1460
- .output('after'),
1461
- )
1427
+ const blockedNodeRunRow = yield* tx
1428
+ .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
1429
+ .content(
1430
+ toNodeRunData(nodeRun, {
1431
+ status: 'blocked',
1432
+ blockedReason: params.message,
1433
+ failureClass: params.failureClass,
1434
+ }),
1435
+ )
1436
+ .output('after')
1437
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
1462
1438
  blockedNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, blockedNodeRunRow, 'plan node run')
1463
1439
  }
1464
1440
 
@@ -1531,12 +1507,11 @@ const promoteDelayedNode = Effect.fn('PlanExecutor.promoteDelayedNode')(function
1531
1507
  planEventDeliveryService,
1532
1508
  run: (tx, emittedEvents) =>
1533
1509
  Effect.gen(function* () {
1534
- const readyNodeRunRow = yield* fromPromise(() =>
1535
- tx
1536
- .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
1537
- .content(toNodeRunData(nodeRun, { status: 'ready', readyAt: nowDate() }))
1538
- .output('after'),
1539
- )
1510
+ const readyNodeRunRow = yield* tx
1511
+ .update(ensureRecordId(nodeRun.id, TABLES.PLAN_NODE_RUN))
1512
+ .content(toNodeRunData(nodeRun, { status: 'ready', readyAt: nowDate() }))
1513
+ .output('after')
1514
+ .pipe(Effect.mapError(toPlanExecutorInternalError))
1540
1515
  const readyNodeRun = yield* parseRowOrFail(PlanNodeRunSchema, readyNodeRunRow, 'plan node run')
1541
1516
 
1542
1517
  const updatedNodeRuns = nodeRuns.map((candidate) =>
@@ -1557,18 +1532,16 @@ const promoteDelayedNode = Effect.fn('PlanExecutor.promoteDelayedNode')(function
1557
1532
  capturedEvents: emittedEvents,
1558
1533
  })
1559
1534
 
1560
- const synced = yield* fromPromise(() =>
1561
- syncRunGraph(context, {
1562
- tx,
1563
- run,
1564
- spec,
1565
- nodeSpecs,
1566
- nodeRuns: updatedNodeRuns,
1567
- artifacts,
1568
- emittedBy: params.emittedBy,
1569
- capturedEvents: emittedEvents,
1570
- }),
1571
- )
1535
+ const synced = yield* syncRunGraph(context, {
1536
+ tx,
1537
+ run,
1538
+ spec,
1539
+ nodeSpecs,
1540
+ nodeRuns: updatedNodeRuns,
1541
+ artifacts,
1542
+ emittedBy: params.emittedBy,
1543
+ capturedEvents: emittedEvents,
1544
+ }).pipe(Effect.mapError(toPlanExecutorInternalError))
1572
1545
 
1573
1546
  const checkpoint = yield* saveCheckpointWithContext(context, {
1574
1547
  tx,