@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
@@ -22,7 +22,7 @@ import type {
22
22
  UpdateAutonomousJobInput,
23
23
  } from '@lota-sdk/shared'
24
24
  import type { Job } from 'bullmq'
25
- import { Context, Cron, Schema, Effect, Layer } from 'effect'
25
+ import { Context, Cron, Schema, Effect, Layer, Ref } from 'effect'
26
26
  import { BoundQuery, RecordId } from 'surrealdb'
27
27
  import { z } from 'zod'
28
28
 
@@ -968,70 +968,72 @@ function executeQueuedRunEffect(
968
968
  deps: AutonomousJobDeps,
969
969
  job: Job<AutonomousJobQueuePayload>,
970
970
  ): Effect.Effect<{ status: string; summary?: string }, AutonomousJobServiceError> {
971
- let autonomousJobRow: AutonomousJobRow | null = null
972
- let runRow: AutonomousJobRunRow | null = null
973
-
974
971
  return Effect.gen(function* () {
975
- yield* effectTryPromise(() => deps.db.connect(), 'Failed to connect to autonomous job database.')
976
- autonomousJobRow = yield* getRowEffect(deps, job.data.autonomousJobId)
977
- const currentJobRow = autonomousJobRow
978
- const { queueJobId, runRow: initialRunRow } = yield* getOrCreateQueuedRunRowEffect(deps, job, currentJobRow)
972
+ const autonomousJobRowRef = yield* Ref.make<AutonomousJobRow | null>(null)
973
+ const runRowRef = yield* Ref.make<AutonomousJobRunRow | null>(null)
974
+
975
+ return yield* Effect.gen(function* () {
976
+ yield* effectTryPromise(() => deps.db.connect(), 'Failed to connect to autonomous job database.')
977
+ const currentJobRow = yield* getRowEffect(deps, job.data.autonomousJobId)
978
+ yield* Ref.set(autonomousJobRowRef, currentJobRow)
979
+ const { queueJobId, runRow: initialRunRow } = yield* getOrCreateQueuedRunRowEffect(deps, job, currentJobRow)
980
+
981
+ yield* Ref.set(runRowRef, initialRunRow)
982
+ if (currentJobRow.status !== 'active' && job.data.trigger === 'scheduled') {
983
+ return { status: 'skipped' }
984
+ }
979
985
 
980
- runRow = initialRunRow
981
- if (currentJobRow.status !== 'active' && job.data.trigger === 'scheduled') {
982
- return { status: 'skipped' }
983
- }
986
+ const currentRunRow = yield* markQueuedRunRunningEffect(deps, initialRunRow, queueJobId)
987
+ yield* Ref.set(runRowRef, currentRunRow)
988
+ const inputMessage = buildSyntheticUserMessage(currentJobRow.prompt)
989
+
990
+ const [thread, turnModule] = yield* Effect.all([
991
+ deps.thread
992
+ .getThread(currentJobRow.threadId)
993
+ .pipe(
994
+ Effect.mapError(
995
+ (cause) => new AutonomousJobServiceError({ message: 'Failed to load autonomous job thread.', cause }),
996
+ ),
997
+ ),
998
+ effectTryPromise<ThreadTurnModule>(() => import('./thread/thread-turn'), 'Failed to load thread turn runtime.'),
999
+ ])
984
1000
 
985
- runRow = yield* markQueuedRunRunningEffect(deps, initialRunRow, queueJobId)
986
- const currentRunRow = runRow
987
- const inputMessage = buildSyntheticUserMessage(currentJobRow.prompt)
1001
+ const turnResult = yield* effectTryPromise(
1002
+ () =>
1003
+ turnModule.runThreadTurnInBackground({
1004
+ thread,
1005
+ threadRef: ensureRecordId(currentJobRow.threadId, TABLES.THREAD),
1006
+ orgRef: ensureRecordId(currentJobRow.organizationId, TABLES.ORGANIZATION),
1007
+ userRef: ensureRecordId(currentJobRow.ownerUserId, TABLES.USER),
1008
+ userName: currentJobRow.ownerUserName,
1009
+ agentIdOverride: currentJobRow.agentId,
1010
+ inputMessage,
1011
+ }),
1012
+ 'Failed to run autonomous job thread turn.',
1013
+ )
988
1014
 
989
- const [thread, turnModule] = yield* Effect.all([
990
- deps.thread
991
- .getThread(currentJobRow.threadId)
1015
+ const activePlan = yield* deps.executionPlan
1016
+ .getActivePlanForThread(currentJobRow.threadId)
992
1017
  .pipe(
993
1018
  Effect.mapError(
994
- (cause) => new AutonomousJobServiceError({ message: 'Failed to load autonomous job thread.', cause }),
1019
+ (cause) => new AutonomousJobServiceError({ message: 'Failed to load active execution plan.', cause }),
995
1020
  ),
996
- ),
997
- effectTryPromise<ThreadTurnModule>(() => import('./thread/thread-turn'), 'Failed to load thread turn runtime.'),
998
- ])
1021
+ )
999
1022
 
1000
- const turnResult = yield* effectTryPromise(
1001
- () =>
1002
- turnModule.runThreadTurnInBackground({
1003
- thread,
1004
- threadRef: ensureRecordId(currentJobRow.threadId, TABLES.THREAD),
1005
- orgRef: ensureRecordId(currentJobRow.organizationId, TABLES.ORGANIZATION),
1006
- userRef: ensureRecordId(currentJobRow.ownerUserId, TABLES.USER),
1007
- userName: currentJobRow.ownerUserName,
1008
- agentIdOverride: currentJobRow.agentId,
1009
- inputMessage,
1023
+ return yield* completeQueuedRunEffect(deps, { job, currentJobRow, currentRunRow, turnResult, activePlan })
1024
+ }).pipe(
1025
+ Effect.catch((error: unknown) =>
1026
+ Effect.gen(function* () {
1027
+ const currentJobRow = yield* Ref.get(autonomousJobRowRef)
1028
+ const currentRunRow = yield* Ref.get(runRowRef)
1029
+ if (!currentJobRow || !currentRunRow) {
1030
+ return yield* new AutonomousJobServiceError({ message: 'Failed to execute autonomous job.', cause: error })
1031
+ }
1032
+ return yield* failQueuedRunEffect(deps, { job, currentJobRow, currentRunRow, error })
1010
1033
  }),
1011
- 'Failed to run autonomous job thread turn.',
1034
+ ),
1012
1035
  )
1013
-
1014
- const activePlan = yield* deps.executionPlan
1015
- .getActivePlanForThread(currentJobRow.threadId)
1016
- .pipe(
1017
- Effect.mapError(
1018
- (cause) => new AutonomousJobServiceError({ message: 'Failed to load active execution plan.', cause }),
1019
- ),
1020
- )
1021
-
1022
- return yield* completeQueuedRunEffect(deps, { job, currentJobRow, currentRunRow, turnResult, activePlan })
1023
- }).pipe(
1024
- Effect.catch((error: unknown) =>
1025
- Effect.gen(function* () {
1026
- const currentJobRow = autonomousJobRow
1027
- const currentRunRow = runRow
1028
- if (!currentJobRow || !currentRunRow) {
1029
- return yield* new AutonomousJobServiceError({ message: 'Failed to execute autonomous job.', cause: error })
1030
- }
1031
- return yield* failQueuedRunEffect(deps, { job, currentJobRow, currentRunRow, error })
1032
- }),
1033
- ),
1034
- )
1036
+ })
1035
1037
  }
1036
1038
 
1037
1039
  function createAutonomousJobService(deps: AutonomousJobDeps) {
@@ -1068,7 +1070,7 @@ export function makeAutonomousJobService(deps: AutonomousJobDeps) {
1068
1070
  export class AutonomousJobServiceTag extends Context.Service<
1069
1071
  AutonomousJobServiceTag,
1070
1072
  ReturnType<typeof makeAutonomousJobService>
1071
- >()('AutonomousJobService') {}
1073
+ >()('@lota-sdk/core/AutonomousJobService') {}
1072
1074
 
1073
1075
  export const AutonomousJobServiceLive = Layer.effect(
1074
1076
  AutonomousJobServiceTag,
@@ -0,0 +1,54 @@
1
+ import { Context, Effect, FiberSet, Layer, Schema } from 'effect'
2
+
3
+ /**
4
+ * Tagged error for the BackgroundWorkService surface. The service itself
5
+ * never returns this — it's reserved for future API extensions.
6
+ */
7
+ export class BackgroundWorkError extends Schema.TaggedErrorClass<BackgroundWorkError>()(
8
+ '@lota-sdk/core/BackgroundWorkError',
9
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
10
+ ) {}
11
+
12
+ /**
13
+ * Supervised fire-and-forget fork pool.
14
+ *
15
+ * `run` schedules an effect on a process-wide FiberSet that is interrupted
16
+ * when the runtime scope closes — replaces ad-hoc `Effect.runFork`,
17
+ * `Effect.runForkWith`, and `Effect.forkDetach` call sites that would
18
+ * otherwise leak fibers across shutdown.
19
+ *
20
+ * Both methods accept any `Effect.Effect<A, E>`; failures are logged via
21
+ * `Effect.logError` and never propagate to the caller. `runForget` ignores
22
+ * both the result and the cause — use it for best-effort metrics, cache
23
+ * touches, and other strictly cosmetic side effects.
24
+ */
25
+ export class BackgroundWorkService extends Context.Service<
26
+ BackgroundWorkService,
27
+ {
28
+ readonly run: <A, E>(effect: Effect.Effect<A, E>, label?: string) => Effect.Effect<void>
29
+ readonly runForget: <A, E>(effect: Effect.Effect<A, E>, label?: string) => Effect.Effect<void>
30
+ }
31
+ >()('@lota-sdk/core/BackgroundWorkService') {}
32
+
33
+ export const BackgroundWorkServiceLive = Layer.effect(
34
+ BackgroundWorkService,
35
+ Effect.gen(function* () {
36
+ const set = yield* FiberSet.make<unknown, unknown>()
37
+
38
+ const run = <A, E>(effect: Effect.Effect<A, E>, label?: string): Effect.Effect<void> =>
39
+ Effect.asVoid(
40
+ FiberSet.run(
41
+ set,
42
+ effect.pipe(
43
+ Effect.withLogSpan(label ?? 'background'),
44
+ Effect.catchCause((cause) => Effect.logError(cause)),
45
+ ),
46
+ ),
47
+ )
48
+
49
+ const runForget = <A, E>(effect: Effect.Effect<A, E>, label?: string): Effect.Effect<void> =>
50
+ Effect.asVoid(FiberSet.run(set, effect.pipe(Effect.withLogSpan(label ?? 'background-forget'), Effect.ignore)))
51
+
52
+ return { run, runForget } as const
53
+ }),
54
+ )
@@ -2,7 +2,9 @@ import { Context, Effect, FiberMap, Layer } from 'effect'
2
2
 
3
3
  import { ChatRunRegistry } from '../runtime/chat-run-registry'
4
4
 
5
- export class ChatRunRegistryTag extends Context.Service<ChatRunRegistryTag, ChatRunRegistry>()('ChatRunRegistry') {}
5
+ export class ChatRunRegistryTag extends Context.Service<ChatRunRegistryTag, ChatRunRegistry>()(
6
+ '@lota-sdk/core/ChatRunRegistry',
7
+ ) {}
6
8
 
7
9
  export const ChatRunRegistryLive = Layer.effect(
8
10
  ChatRunRegistryTag,
@@ -146,7 +146,7 @@ export function makeContextCompactionService(deps: ContextCompactionDeps) {
146
146
  export class ContextCompactionServiceTag extends Context.Service<
147
147
  ContextCompactionServiceTag,
148
148
  ReturnType<typeof makeContextCompactionService>
149
- >()('ContextCompactionService') {}
149
+ >()('@lota-sdk/core/ContextCompactionService') {}
150
150
 
151
151
  export const ContextCompactionServiceLive = Layer.effect(
152
152
  ContextCompactionServiceTag,
@@ -2,6 +2,7 @@ import { Context, Schema, Effect, Layer } from 'effect'
2
2
 
3
3
  import { chunkMarkdownDocument, chunkPagedDocument, chunkPlainTextDocument } from '../document/org-document-chunking'
4
4
  import type { ParsedDocumentChunk } from '../document/org-document-chunking'
5
+ import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
5
6
  import { RuntimeConfigServiceTag } from '../effect/services'
6
7
  import { ProviderEmbeddings } from '../embeddings/provider'
7
8
  import { sha256Hex } from '../utils/crypto'
@@ -71,25 +72,15 @@ class DocumentChunkServiceError extends Schema.TaggedErrorClass<DocumentChunkSer
71
72
  { message: Schema.String, cause: Schema.Defect },
72
73
  ) {}
73
74
 
75
+ const effectTryDocumentChunkPromise = makeEffectTryPromiseWithMessage(
76
+ (message, cause) => new DocumentChunkServiceError({ message, cause }),
77
+ )
78
+
74
79
  function tryDocumentChunkPromise<A>(
75
80
  message: string,
76
- thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
81
+ evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
77
82
  ): Effect.Effect<A, DocumentChunkServiceError> {
78
- return Effect.suspend(() => {
79
- try {
80
- const value = thunk()
81
- if (Effect.isEffect(value)) {
82
- return value.pipe(Effect.mapError((cause) => new DocumentChunkServiceError({ message, cause })))
83
- }
84
-
85
- return Effect.tryPromise({
86
- try: () => Promise.resolve(value),
87
- catch: (cause) => new DocumentChunkServiceError({ message, cause }),
88
- })
89
- } catch (cause) {
90
- return Effect.fail(new DocumentChunkServiceError({ message, cause }))
91
- }
92
- })
83
+ return effectTryDocumentChunkPromise(evaluate, message)
93
84
  }
94
85
 
95
86
  export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): DocumentChunkService {
@@ -200,7 +191,7 @@ export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): D
200
191
  }
201
192
 
202
193
  export class DocumentChunkServiceTag extends Context.Service<DocumentChunkServiceTag, DocumentChunkService>()(
203
- 'DocumentChunkService',
194
+ '@lota-sdk/core/DocumentChunkService',
204
195
  ) {}
205
196
 
206
197
  export const DocumentChunkServiceLive = Layer.effect(
@@ -8,17 +8,30 @@ import {
8
8
  } from '@lota-sdk/shared'
9
9
  import { Effect } from 'effect'
10
10
  import { RecordId } from 'surrealdb'
11
+ import type { z } from 'zod'
11
12
 
12
13
  import type { RecordIdInput } from '../../db/record-id'
13
14
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
14
15
  import type { DatabaseTransaction } from '../../db/service'
15
16
  import { TABLES } from '../../db/tables'
17
+ import { ServiceError } from '../../effect/errors'
16
18
  import { runPromise } from '../../effect/runtime'
17
19
  import { nowDate } from '../../utils/date-time'
18
20
  import type { CompiledPlanNode } from '../plan/plan-compiler.service'
19
21
  import type { syncRunGraph } from '../plan/plan-executor-graph'
20
22
  import { toRunData } from '../plan/plan-run-data'
21
23
 
24
+ function parseRowOrFail<TSchema extends z.ZodTypeAny>(
25
+ schema: TSchema,
26
+ value: unknown,
27
+ operation: string,
28
+ ): Effect.Effect<z.infer<TSchema>, ServiceError> {
29
+ return Effect.try({
30
+ try: () => schema.parse(value) as z.infer<TSchema>,
31
+ catch: (cause) => new ServiceError({ message: `Failed to parse row for ${operation}.`, cause }),
32
+ })
33
+ }
34
+
22
35
  type SyncRunGraphParams = Parameters<typeof syncRunGraph>[1]
23
36
  type SyncRunGraphResult = Awaited<ReturnType<typeof syncRunGraph>>
24
37
 
@@ -86,7 +99,7 @@ function createNodeSpecs(tx: DatabaseTransaction, planSpecId: RecordIdInput, nod
86
99
  .create(nodeSpecId)
87
100
  .content(buildNodeSpecCreateContent(compiledNode, planSpecId))
88
101
  .output('after')
89
- createdRecords.push(PlanNodeSpecRecordSchema.parse(created))
102
+ createdRecords.push(yield* parseRowOrFail(PlanNodeSpecRecordSchema, created, 'createNodeSpecs'))
90
103
  }
91
104
 
92
105
  return createdRecords
@@ -109,7 +122,7 @@ function createNodeRuns(
109
122
  .create(nodeRunId)
110
123
  .content(buildNodeRunCreateContent(runId, planSpecId, nodeSpec))
111
124
  .output('after')
112
- createdNodeRuns.push(PlanNodeRunSchema.parse(created))
125
+ createdNodeRuns.push(yield* parseRowOrFail(PlanNodeRunSchema, created, 'createNodeRuns'))
113
126
  }
114
127
 
115
128
  return createdNodeRuns
@@ -138,26 +151,25 @@ export function createInitializedRunGraph(params: {
138
151
  return runPromise(
139
152
  Effect.gen(function* () {
140
153
  const nodeSpecs = yield* createNodeSpecs(params.tx, params.spec.id, params.nodes)
141
- const run = PlanRunSchema.parse(
142
- yield* params.tx
143
- .create(ensureRecordId(params.runId, TABLES.PLAN_RUN))
144
- .content({
145
- planSpecId: ensureRecordId(params.spec.id, TABLES.PLAN_SPEC),
146
- organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
147
- threadId: ensureRecordId(params.threadId, TABLES.THREAD),
148
- ...(params.sourceThreadId ? { sourceThreadId: ensureRecordId(params.sourceThreadId, TABLES.THREAD) } : {}),
149
- leadAgentId: params.leadAgentId,
150
- ...(params.createdByAgentId ? { createdByAgentId: params.createdByAgentId } : {}),
151
- status: params.requireApproval ? 'pending-approval' : 'running',
152
- readyNodeIds: [],
153
- failureCount: 0,
154
- ...(params.runPatch?.replacedRunId
155
- ? { replacedRunId: ensureRecordId(params.runPatch.replacedRunId, TABLES.PLAN_RUN) }
156
- : {}),
157
- ...(params.requireApproval ? {} : { startedAt: nowDate() }),
158
- })
159
- .output('after'),
160
- )
154
+ const runRaw = yield* params.tx
155
+ .create(ensureRecordId(params.runId, TABLES.PLAN_RUN))
156
+ .content({
157
+ planSpecId: ensureRecordId(params.spec.id, TABLES.PLAN_SPEC),
158
+ organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
159
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
160
+ ...(params.sourceThreadId ? { sourceThreadId: ensureRecordId(params.sourceThreadId, TABLES.THREAD) } : {}),
161
+ leadAgentId: params.leadAgentId,
162
+ ...(params.createdByAgentId ? { createdByAgentId: params.createdByAgentId } : {}),
163
+ status: params.requireApproval ? 'pending-approval' : 'running',
164
+ readyNodeIds: [],
165
+ failureCount: 0,
166
+ ...(params.runPatch?.replacedRunId
167
+ ? { replacedRunId: ensureRecordId(params.runPatch.replacedRunId, TABLES.PLAN_RUN) }
168
+ : {}),
169
+ ...(params.requireApproval ? {} : { startedAt: nowDate() }),
170
+ })
171
+ .output('after')
172
+ const run = yield* parseRowOrFail(PlanRunSchema, runRaw, 'createInitializedRunGraph:run')
161
173
 
162
174
  const nodeRuns = yield* createNodeRuns(params.tx, run.id, params.spec.id, nodeSpecs)
163
175
  const synced = params.requireApproval
@@ -186,7 +198,7 @@ export function createInitializedRunGraph(params: {
186
198
  detail: { ...params.createdEventDetail, nodeCount: nodeSpecs.length },
187
199
  })
188
200
  .output('after')
189
- params.emittedEvents.push(PlanEventSchema.parse(event))
201
+ params.emittedEvents.push(yield* parseRowOrFail(PlanEventSchema, event, 'createInitializedRunGraph:createdEvent'))
190
202
 
191
203
  if (params.requireApproval) {
192
204
  const pendingApprovalEvent = yield* params.tx
@@ -201,38 +213,46 @@ export function createInitializedRunGraph(params: {
201
213
  detail: { ...params.createdEventDetail, nodeCount: nodeSpecs.length },
202
214
  })
203
215
  .output('after')
204
- params.emittedEvents.push(PlanEventSchema.parse(pendingApprovalEvent))
216
+ params.emittedEvents.push(
217
+ yield* parseRowOrFail(
218
+ PlanEventSchema,
219
+ pendingApprovalEvent,
220
+ 'createInitializedRunGraph:pendingApprovalEvent',
221
+ ),
222
+ )
205
223
  }
206
224
 
207
- const checkpoint = PlanCheckpointSchema.parse(
208
- yield* params.tx
209
- .create(new RecordId(TABLES.PLAN_CHECKPOINT, Bun.randomUUIDv7()))
210
- .content({
211
- runId: ensureRecordId(synced.run.id, TABLES.PLAN_RUN),
212
- sequence: 1,
213
- runStatus: synced.run.status,
214
- readyNodeIds: [...synced.run.readyNodeIds],
215
- activeNodeIds: synced.run.currentNodeId ? [synced.run.currentNodeId] : [],
216
- artifactIds: [],
217
- lastCompletedNodeIds: synced.nodeRuns
218
- .filter((nodeRun) => nodeRun.status === 'completed' || nodeRun.status === 'partial')
219
- .map((nodeRun) => nodeRun.nodeId),
220
- snapshot: {
221
- reason: params.checkpointReason,
222
- currentNodeId: synced.run.currentNodeId,
223
- waitingNodeId: synced.run.waitingNodeId,
224
- readyNodeIds: synced.run.readyNodeIds,
225
- },
226
- })
227
- .output('after'),
225
+ const checkpointRaw = yield* params.tx
226
+ .create(new RecordId(TABLES.PLAN_CHECKPOINT, Bun.randomUUIDv7()))
227
+ .content({
228
+ runId: ensureRecordId(synced.run.id, TABLES.PLAN_RUN),
229
+ sequence: 1,
230
+ runStatus: synced.run.status,
231
+ readyNodeIds: [...synced.run.readyNodeIds],
232
+ activeNodeIds: synced.run.currentNodeId ? [synced.run.currentNodeId] : [],
233
+ artifactIds: [],
234
+ lastCompletedNodeIds: synced.nodeRuns
235
+ .filter((nodeRun) => nodeRun.status === 'completed' || nodeRun.status === 'partial')
236
+ .map((nodeRun) => nodeRun.nodeId),
237
+ snapshot: {
238
+ reason: params.checkpointReason,
239
+ currentNodeId: synced.run.currentNodeId,
240
+ waitingNodeId: synced.run.waitingNodeId,
241
+ readyNodeIds: synced.run.readyNodeIds,
242
+ },
243
+ })
244
+ .output('after')
245
+ const checkpoint = yield* parseRowOrFail(
246
+ PlanCheckpointSchema,
247
+ checkpointRaw,
248
+ 'createInitializedRunGraph:checkpoint',
228
249
  )
229
250
 
230
- const updatedRun = PlanRunSchema.parse(
231
- yield* params.tx
232
- .update(ensureRecordId(synced.run.id, TABLES.PLAN_RUN))
233
- .content(toRunData(synced.run, { lastCheckpointId: checkpoint.id }))
234
- .output('after'),
235
- )
251
+ const updatedRunRaw = yield* params.tx
252
+ .update(ensureRecordId(synced.run.id, TABLES.PLAN_RUN))
253
+ .content(toRunData(synced.run, { lastCheckpointId: checkpoint.id }))
254
+ .output('after')
255
+ const updatedRun = yield* parseRowOrFail(PlanRunSchema, updatedRunRaw, 'createInitializedRunGraph:updatedRun')
236
256
 
237
257
  const checkpointEvent = yield* params.tx
238
258
  .create(new RecordId(TABLES.PLAN_EVENT, Bun.randomUUIDv7()))
@@ -248,7 +268,9 @@ export function createInitializedRunGraph(params: {
248
268
  },
249
269
  })
250
270
  .output('after')
251
- params.emittedEvents.push(PlanEventSchema.parse(checkpointEvent))
271
+ params.emittedEvents.push(
272
+ yield* parseRowOrFail(PlanEventSchema, checkpointEvent, 'createInitializedRunGraph:checkpointEvent'),
273
+ )
252
274
 
253
275
  return updatedRun
254
276
  }),
@@ -1010,7 +1010,7 @@ export function makeExecutionPlanService(deps: ExecutionPlanDeps) {
1010
1010
  export class ExecutionPlanServiceTag extends Context.Service<
1011
1011
  ExecutionPlanServiceTag,
1012
1012
  ReturnType<typeof makeExecutionPlanService>
1013
- >()('ExecutionPlanService') {}
1013
+ >()('@lota-sdk/core/ExecutionPlanService') {}
1014
1014
 
1015
1015
  export const ExecutionPlanServiceLive = Layer.effect(
1016
1016
  ExecutionPlanServiceTag,
@@ -141,7 +141,7 @@ export function makeFeedbackLoopService(planRunService: ReturnType<typeof makePl
141
141
  export class FeedbackLoopServiceTag extends Context.Service<
142
142
  FeedbackLoopServiceTag,
143
143
  ReturnType<typeof makeFeedbackLoopService>
144
- >()('FeedbackLoopService') {}
144
+ >()('@lota-sdk/core/FeedbackLoopService') {}
145
145
 
146
146
  export const FeedbackLoopServiceLive = Layer.effect(
147
147
  FeedbackLoopServiceTag,
@@ -1,10 +1,17 @@
1
- import type { ConvergenceState } from '@lota-sdk/shared'
1
+ import type {
2
+ ConvergenceState,
3
+ ExecutionMode,
4
+ PlanNodeResultSubmission,
5
+ PlanNodeRunRecord,
6
+ PlanNodeSpecRecord,
7
+ PlanRunRecord,
8
+ PlanSpecRecord,
9
+ } from '@lota-sdk/shared'
2
10
  import { Context, Effect, Layer } from 'effect'
3
11
 
4
12
  import { runPromise } from '../effect/runtime'
5
- import { getCurrentRuntime } from '../effect/runtime-ref'
6
13
  import { routeGraphFullEffect } from './graph-full-routing'
7
- import type { makeOwnershipDispatcherService } from './ownership-dispatcher.service'
14
+ import type { OwnershipDispatcherService } from './ownership-dispatcher.service'
8
15
  import { OwnershipDispatcherServiceTag } from './ownership-dispatcher.service'
9
16
  import type { makePlanExecutorService } from './plan/plan-executor.service'
10
17
  import { PlanExecutorServiceTag } from './plan/plan-executor.service'
@@ -48,18 +55,30 @@ function decideRerouteAction(params: {
48
55
  }
49
56
 
50
57
  interface GlobalOrchestratorDeps {
51
- ownershipDispatcherService: ReturnType<typeof makeOwnershipDispatcherService>
58
+ ownershipDispatcherService: Pick<OwnershipDispatcherService, 'dispatchReadyNode'>
52
59
  planExecutorService: ReturnType<typeof makePlanExecutorService>
53
60
  planRunService: ReturnType<typeof makePlanRunService>
54
61
  }
55
62
 
63
+ type DispatchReadyNodeParams = {
64
+ run: PlanRunRecord
65
+ nodeSpecRecord: PlanNodeSpecRecord
66
+ nodeRun: PlanNodeRunRecord
67
+ spec: PlanSpecRecord
68
+ executionModeOverride?: ExecutionMode
69
+ }
70
+
71
+ type DispatchReadyNode = (params: DispatchReadyNodeParams) => Effect.Effect<PlanNodeResultSubmission, unknown, never>
72
+
56
73
  export function makeGlobalOrchestratorService(deps: GlobalOrchestratorDeps) {
74
+ const dispatchReadyNode: DispatchReadyNode = (params) => deps.ownershipDispatcherService.dispatchReadyNode(params)
75
+
57
76
  return {
58
77
  detectConvergence,
59
78
  decideRerouteAction,
60
79
  routeGraphFull: (params: { threadId: string; runId: string }) =>
61
80
  routeGraphFullEffect(params, {
62
- dispatchReadyNode: (dispatchParams) => deps.ownershipDispatcherService.dispatchReadyNode(dispatchParams),
81
+ dispatchReadyNode,
63
82
  planExecutorService: deps.planExecutorService,
64
83
  planRunService: deps.planRunService,
65
84
  }),
@@ -69,7 +88,7 @@ export function makeGlobalOrchestratorService(deps: GlobalOrchestratorDeps) {
69
88
  export class GlobalOrchestratorServiceTag extends Context.Service<
70
89
  GlobalOrchestratorServiceTag,
71
90
  ReturnType<typeof makeGlobalOrchestratorService>
72
- >()('GlobalOrchestratorService') {}
91
+ >()('@lota-sdk/core/GlobalOrchestratorService') {}
73
92
 
74
93
  export const GlobalOrchestratorServiceLive = Layer.effect(
75
94
  GlobalOrchestratorServiceTag,
@@ -81,10 +100,14 @@ export const GlobalOrchestratorServiceLive = Layer.effect(
81
100
  }),
82
101
  )
83
102
 
84
- function getGlobalOrchestratorService() {
85
- return getCurrentRuntime().runSync(Effect.service(GlobalOrchestratorServiceTag))
86
- }
103
+ const routeGraphFullWithRuntime = Effect.fn('GlobalOrchestrator.routeGraphFullWithRuntime')(function* (params: {
104
+ threadId: string
105
+ runId: string
106
+ }) {
107
+ const globalOrchestratorService = yield* GlobalOrchestratorServiceTag
108
+ return yield* globalOrchestratorService.routeGraphFull(params)
109
+ })
87
110
 
88
111
  export function routeGraphFull(params: { threadId: string; runId: string }): Promise<void> {
89
- return runPromise(getGlobalOrchestratorService().routeGraphFull(params))
112
+ return runPromise(routeGraphFullWithRuntime(params))
90
113
  }