@lota-sdk/core 0.4.13 → 0.4.15

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/config/constants.ts +1 -1
  8. package/src/create-runtime.ts +259 -200
  9. package/src/db/cursor-pagination.ts +2 -9
  10. package/src/db/memory-store.ts +194 -175
  11. package/src/db/memory.ts +125 -71
  12. package/src/db/schema-fingerprint.ts +5 -4
  13. package/src/db/service-normalization.ts +4 -3
  14. package/src/db/service.ts +3 -2
  15. package/src/db/startup.ts +15 -16
  16. package/src/effect/errors.ts +161 -21
  17. package/src/effect/index.ts +0 -1
  18. package/src/embeddings/provider.ts +15 -7
  19. package/src/queues/autonomous-job.queue.ts +10 -22
  20. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  21. package/src/queues/document-processor.queue.ts +13 -4
  22. package/src/queues/memory-consolidation.queue.ts +26 -14
  23. package/src/queues/plan-agent-heartbeat.queue.ts +48 -31
  24. package/src/queues/plan-scheduler.queue.ts +37 -15
  25. package/src/queues/queue-factory.ts +59 -35
  26. package/src/queues/standalone-worker.ts +3 -2
  27. package/src/redis/connection.ts +10 -3
  28. package/src/redis/org-memory-lock.ts +1 -1
  29. package/src/redis/redis-lease-lock.ts +5 -5
  30. package/src/redis/stream-context.ts +1 -1
  31. package/src/runtime/chat-message.ts +64 -1
  32. package/src/runtime/chat-run-orchestration.ts +33 -20
  33. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  34. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  35. package/src/runtime/domain-layer.ts +19 -13
  36. package/src/runtime/execution-plan.ts +7 -3
  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 +22 -24
  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 +83 -92
  103. package/src/services/thread/thread-turn.ts +18 -16
  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 +11 -7
  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
@@ -22,8 +22,8 @@ export class BackgroundWorkError extends Schema.TaggedErrorClass<BackgroundWorkE
22
22
  * both the result and the cause — use it for best-effort metrics, cache
23
23
  * touches, and other strictly cosmetic side effects.
24
24
  */
25
- export class BackgroundWorkService extends Context.Service<
26
- BackgroundWorkService,
25
+ export class BackgroundWorkServiceTag extends Context.Service<
26
+ BackgroundWorkServiceTag,
27
27
  {
28
28
  readonly run: <A, E>(effect: Effect.Effect<A, E>, label?: string) => Effect.Effect<void>
29
29
  readonly runForget: <A, E>(effect: Effect.Effect<A, E>, label?: string) => Effect.Effect<void>
@@ -31,7 +31,7 @@ export class BackgroundWorkService extends Context.Service<
31
31
  >()('@lota-sdk/core/BackgroundWorkService') {}
32
32
 
33
33
  export const BackgroundWorkServiceLive = Layer.effect(
34
- BackgroundWorkService,
34
+ BackgroundWorkServiceTag,
35
35
  Effect.gen(function* () {
36
36
  const set = yield* FiberSet.make<unknown, unknown>()
37
37
 
@@ -1,6 +1,8 @@
1
1
  import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import { Context, Effect, Layer } from 'effect'
3
3
 
4
+ import { AiGatewayModelsTag } from '../ai-gateway/ai-gateway'
5
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
4
6
  import { chatLogger } from '../config/logger'
5
7
  import type { RecordIdRef } from '../db/record-id'
6
8
  import { recordIdToString } from '../db/record-id'
@@ -43,12 +45,14 @@ interface ContextCompactionDeps {
43
45
  redis: RedisConnectionManager
44
46
  threadMessageService: ReturnType<typeof makeThreadMessageService>
45
47
  helperModelRuntime: HelperModelRuntime
48
+ aiGatewayModels: AiGatewayModels
46
49
  }
47
50
 
48
51
  export function makeContextCompactionService(deps: ContextCompactionDeps) {
49
- const { db, redis, threadMessageService, helperModelRuntime } = deps
52
+ const { db, redis, threadMessageService, helperModelRuntime, aiGatewayModels } = deps
50
53
  const contextCompactionRuntime = createWiredContextCompactionRuntime({
51
54
  helperModelRuntime,
55
+ aiGatewayModels,
52
56
  now: nowEpochMillis,
53
57
  randomId: () => Bun.randomUUIDv7(),
54
58
  })
@@ -153,6 +157,7 @@ export const ContextCompactionServiceLive = Layer.effect(
153
157
  const redis = yield* RedisServiceTag
154
158
  const threadMessageService = yield* ThreadMessageServiceTag
155
159
  const helperModelRuntime = yield* HelperModelTag
156
- return makeContextCompactionService({ db, redis, threadMessageService, helperModelRuntime })
160
+ const aiGatewayModels = yield* AiGatewayModelsTag
161
+ return makeContextCompactionService({ db, redis, threadMessageService, helperModelRuntime, aiGatewayModels })
157
162
  }),
158
163
  )
@@ -1,8 +1,9 @@
1
1
  import { Context, Schema, Effect, Layer } from 'effect'
2
2
 
3
+ import { RuntimeBridgeTag } from '../ai-gateway/ai-gateway'
4
+ import type { RuntimeBridge } from '../ai-gateway/ai-gateway'
3
5
  import { chunkMarkdownDocument, chunkPagedDocument, chunkPlainTextDocument } from '../document/org-document-chunking'
4
6
  import type { ParsedDocumentChunk } from '../document/org-document-chunking'
5
- import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
6
7
  import { RuntimeConfigServiceTag } from '../effect/services'
7
8
  import { ProviderEmbeddings } from '../embeddings/provider'
8
9
  import { sha256Hex } from '../utils/crypto'
@@ -13,8 +14,12 @@ type DocumentChunkEmbeddings = {
13
14
  embedQuery(query: string): Promise<number[]>
14
15
  }
15
16
 
16
- function createDocumentChunkEmbeddings(embeddingModel: string, openRouterApiKey?: string): DocumentChunkEmbeddings {
17
- const embeddings = new ProviderEmbeddings({ modelId: embeddingModel, openRouterApiKey })
17
+ function createDocumentChunkEmbeddings(
18
+ embeddingModel: string,
19
+ runPromise: RuntimeBridge['runPromise'],
20
+ openRouterApiKey?: string,
21
+ ): DocumentChunkEmbeddings {
22
+ const embeddings = new ProviderEmbeddings({ modelId: embeddingModel, openRouterApiKey, runPromise })
18
23
 
19
24
  return {
20
25
  embedDocuments: (documents) => embeddings.embedDocuments(documents),
@@ -68,21 +73,10 @@ export interface DocumentChunkService {
68
73
  }
69
74
 
70
75
  class DocumentChunkServiceError extends Schema.TaggedErrorClass<DocumentChunkServiceError>()(
71
- 'DocumentChunkServiceError',
76
+ '@lota-sdk/core/DocumentChunkServiceError',
72
77
  { message: Schema.String, cause: Schema.Defect },
73
78
  ) {}
74
79
 
75
- const effectTryDocumentChunkPromise = makeEffectTryPromiseWithMessage(
76
- (message, cause) => new DocumentChunkServiceError({ message, cause }),
77
- )
78
-
79
- function tryDocumentChunkPromise<A>(
80
- message: string,
81
- evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
82
- ): Effect.Effect<A, DocumentChunkServiceError> {
83
- return effectTryDocumentChunkPromise(evaluate, message)
84
- }
85
-
86
80
  export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): DocumentChunkService {
87
81
  return {
88
82
  buildChunks(params) {
@@ -116,10 +110,15 @@ export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): D
116
110
  syncVersionedChunks(params) {
117
111
  return Effect.gen(function* () {
118
112
  const [existingRows, embeddedChunks] = yield* Effect.all([
119
- tryDocumentChunkPromise('Failed to load existing document chunks.', () => params.loadExisting()),
120
- tryDocumentChunkPromise('Failed to embed document chunks.', () =>
121
- embeddings.embedDocuments(params.chunks.map((chunk) => chunk.content)),
122
- ),
113
+ Effect.tryPromise({
114
+ try: () => params.loadExisting(),
115
+ catch: (cause) =>
116
+ new DocumentChunkServiceError({ message: 'Failed to load existing document chunks.', cause }),
117
+ }),
118
+ Effect.tryPromise({
119
+ try: () => embeddings.embedDocuments(params.chunks.map((chunk) => chunk.content)),
120
+ catch: (cause) => new DocumentChunkServiceError({ message: 'Failed to embed document chunks.', cause }),
121
+ }),
123
122
  ])
124
123
 
125
124
  const existingByChunkKey = new Map(
@@ -132,9 +131,11 @@ export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): D
132
131
  (row) => params.selectShape(row).sourceVersionKey !== params.sourceVersionKey,
133
132
  )
134
133
 
135
- yield* tryDocumentChunkPromise('Failed to archive stale document chunks.', () =>
136
- params.archive(staleVersionRows),
137
- )
134
+ yield* Effect.tryPromise({
135
+ try: () => params.archive(staleVersionRows),
136
+ catch: (cause) =>
137
+ new DocumentChunkServiceError({ message: 'Failed to archive stale document chunks.', cause }),
138
+ })
138
139
 
139
140
  yield* Effect.forEach(
140
141
  params.chunks,
@@ -152,9 +153,14 @@ export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): D
152
153
  seenChunkKeys.add(chunk.chunkKey)
153
154
 
154
155
  if (!existingRow) {
155
- yield* tryDocumentChunkPromise(`Failed to create document chunk ${chunk.chunkKey}.`, () =>
156
- params.create(payload),
157
- )
156
+ yield* Effect.tryPromise({
157
+ try: () => params.create(payload),
158
+ catch: (cause) =>
159
+ new DocumentChunkServiceError({
160
+ message: `Failed to create document chunk ${chunk.chunkKey}.`,
161
+ cause,
162
+ }),
163
+ })
158
164
  return
159
165
  }
160
166
 
@@ -170,9 +176,14 @@ export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): D
170
176
  return
171
177
  }
172
178
 
173
- yield* tryDocumentChunkPromise(`Failed to update document chunk ${chunk.chunkKey}.`, () =>
174
- params.update(existingRow, payload),
175
- )
179
+ yield* Effect.tryPromise({
180
+ try: () => params.update(existingRow, payload),
181
+ catch: (cause) =>
182
+ new DocumentChunkServiceError({
183
+ message: `Failed to update document chunk ${chunk.chunkKey}.`,
184
+ cause,
185
+ }),
186
+ })
176
187
  }),
177
188
  { concurrency: 'unbounded', discard: true },
178
189
  )
@@ -182,9 +193,11 @@ export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): D
182
193
  return current.sourceVersionKey === params.sourceVersionKey && !seenChunkKeys.has(current.chunkKey)
183
194
  })
184
195
 
185
- yield* tryDocumentChunkPromise('Failed to archive removed current-version chunks.', () =>
186
- params.archive(removedCurrentVersionRows),
187
- )
196
+ yield* Effect.tryPromise({
197
+ try: () => params.archive(removedCurrentVersionRows),
198
+ catch: (cause) =>
199
+ new DocumentChunkServiceError({ message: 'Failed to archive removed current-version chunks.', cause }),
200
+ })
188
201
  })
189
202
  },
190
203
  }
@@ -198,8 +211,13 @@ export const DocumentChunkServiceLive = Layer.effect(
198
211
  DocumentChunkServiceTag,
199
212
  Effect.gen(function* () {
200
213
  const runtimeConfig = yield* RuntimeConfigServiceTag
214
+ const bridge = yield* RuntimeBridgeTag
201
215
  return makeDocumentChunkService(
202
- createDocumentChunkEmbeddings(runtimeConfig.aiGateway.embeddingModel, runtimeConfig.aiGateway.openRouterApiKey),
216
+ createDocumentChunkEmbeddings(
217
+ runtimeConfig.aiGateway.embeddingModel,
218
+ bridge.runPromise,
219
+ runtimeConfig.aiGateway.openRouterApiKey,
220
+ ),
203
221
  )
204
222
  }),
205
223
  )
@@ -5,14 +5,16 @@ import type { RecordIdInput } from '../../db/record-id'
5
5
  import { ensureRecordId } from '../../db/record-id'
6
6
  import type { SurrealDBService } from '../../db/service'
7
7
  import { TABLES } from '../../db/tables'
8
+ import { ERROR_TAGS } from '../../effect/errors'
8
9
  import { toDatabaseDateTime } from '../../utils/date-time'
9
10
  import { toRunData } from '../plan/plan-run-data'
10
11
  import type { makePlanRunService } from '../plan/plan-run.service'
11
12
  import type { makePlanSchedulerService } from '../plan/plan-scheduler.service'
12
13
 
13
- class PlanScheduleAttachError extends Schema.TaggedErrorClass<PlanScheduleAttachError>()('PlanScheduleAttachError', {
14
- cause: Schema.String,
15
- }) {}
14
+ class PlanScheduleAttachError extends Schema.TaggedErrorClass<PlanScheduleAttachError>()(
15
+ ERROR_TAGS.PlanScheduleAttachError,
16
+ { cause: Schema.String },
17
+ ) {}
16
18
 
17
19
  function toPlanScheduleAttachError(operation: string, cause: unknown) {
18
20
  const detail = cause instanceof Error ? cause.message : String(cause)
@@ -17,7 +17,6 @@ import { ensureRecordId, recordIdToString } from '../../db/record-id'
17
17
  import type { SurrealDBService } from '../../db/service'
18
18
  import { TABLES } from '../../db/tables'
19
19
  import { BadRequestError } from '../../effect/errors'
20
- import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
21
20
  import { DatabaseServiceTag } from '../../effect/services'
22
21
  import { extractMessageText } from '../../runtime/thread-chat-helpers'
23
22
  import { nowDate } from '../../utils/date-time'
@@ -77,13 +76,13 @@ function saveCheckpointWithService(
77
76
  }
78
77
 
79
78
  class ExecutionPlanServiceError extends Schema.TaggedErrorClass<ExecutionPlanServiceError>()(
80
- 'ExecutionPlanServiceError',
79
+ '@lota-sdk/core/ExecutionPlanServiceError',
81
80
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
82
81
  ) {}
83
82
 
84
- const effectTryPromise = makeEffectTryPromiseWithMessage(
85
- (message, cause) => new ExecutionPlanServiceError({ message, cause }),
86
- )
83
+ function toExecutionPlanServiceError(message: string, cause: unknown): ExecutionPlanServiceError {
84
+ return new ExecutionPlanServiceError({ message, cause })
85
+ }
87
86
 
88
87
  function parseRowEffect<TSchema extends z.ZodTypeAny>(
89
88
  schema: TSchema,
@@ -316,62 +315,59 @@ function createPlanEffect(
316
315
  planEventDeliveryService: deps.planEventDelivery,
317
316
  run: (tx, emittedEvents) =>
318
317
  Effect.gen(function* () {
319
- const createdSpec = yield* effectTryPromise(
320
- () =>
321
- tx
322
- .create(specId)
323
- .content(
324
- buildCompiledSpecCreateData({
325
- organizationId: params.organizationId,
326
- threadId: params.threadId,
327
- leadAgentId: params.leadAgentId,
328
- compiled,
329
- version: 1,
330
- }),
331
- )
332
- .output('after'),
333
- 'Failed to create execution plan spec.',
334
- )
335
- const spec = yield* parseRowEffect(PlanSpecSchema, createdSpec, 'created plan spec')
336
-
337
- yield* effectTryPromise(
338
- () =>
339
- createInitializedRunGraph({
340
- tx,
341
- runId,
342
- spec,
318
+ const createdSpec = yield* tx
319
+ .create(specId)
320
+ .content(
321
+ buildCompiledSpecCreateData({
343
322
  organizationId: params.organizationId,
344
323
  threadId: params.threadId,
345
- sourceThreadId: params.sourceThreadId,
346
324
  leadAgentId: params.leadAgentId,
347
- createdByAgentId: params.createdByAgentId,
348
- requireApproval,
349
- nodes: compiled.nodes,
350
- emittedEvents,
351
- createdEventType: 'plan-created',
352
- createdEventMessage: `Created execution plan "${spec.title}".`,
353
- createdEventDetail: { title: spec.title, objective: spec.objective },
354
- checkpointReason: 'plan-created',
355
- planExecutor: deps.planExecutor,
325
+ compiled,
326
+ version: 1,
356
327
  }),
357
- 'Failed to initialize execution plan graph.',
328
+ )
329
+ .output('after')
330
+ .pipe(
331
+ Effect.mapError((cause) => toExecutionPlanServiceError('Failed to create execution plan spec.', cause)),
332
+ )
333
+ const spec = yield* parseRowEffect(PlanSpecSchema, createdSpec, 'created plan spec')
334
+
335
+ yield* createInitializedRunGraph({
336
+ tx,
337
+ runId,
338
+ spec,
339
+ organizationId: params.organizationId,
340
+ threadId: params.threadId,
341
+ sourceThreadId: params.sourceThreadId,
342
+ leadAgentId: params.leadAgentId,
343
+ createdByAgentId: params.createdByAgentId,
344
+ requireApproval,
345
+ nodes: compiled.nodes,
346
+ emittedEvents,
347
+ createdEventType: 'plan-created',
348
+ createdEventMessage: `Created execution plan "${spec.title}".`,
349
+ createdEventDetail: { title: spec.title, objective: spec.objective },
350
+ checkpointReason: 'plan-created',
351
+ planExecutor: deps.planExecutor,
352
+ }).pipe(
353
+ Effect.mapError((cause) =>
354
+ toExecutionPlanServiceError('Failed to initialize execution plan graph.', cause),
355
+ ),
358
356
  )
359
357
  }),
360
358
  })
361
359
 
362
360
  if (!requireApproval) {
363
- yield* effectTryPromise(
364
- () =>
365
- attachPlanScheduleIfNeeded({
366
- db: databaseService,
367
- planRunService: deps.planRun,
368
- planSchedulerService: deps.planScheduler,
369
- organizationId: params.organizationId,
370
- threadId: params.threadId,
371
- runId,
372
- planSpecId: specId,
373
- }),
374
- 'Failed to attach execution plan schedule.',
361
+ yield* attachPlanScheduleIfNeeded({
362
+ db: databaseService,
363
+ planRunService: deps.planRun,
364
+ planSchedulerService: deps.planScheduler,
365
+ organizationId: params.organizationId,
366
+ threadId: params.threadId,
367
+ runId,
368
+ planSpecId: specId,
369
+ }).pipe(
370
+ Effect.mapError((cause) => toExecutionPlanServiceError('Failed to attach execution plan schedule.', cause)),
375
371
  )
376
372
  }
377
373
 
@@ -425,95 +421,94 @@ function replacePlanEffect(
425
421
  planEventDeliveryService: deps.planEventDelivery,
426
422
  run: (tx, emittedEvents) =>
427
423
  Effect.gen(function* () {
428
- const updatedSpec = yield* effectTryPromise(
429
- () =>
430
- tx
431
- .update(ensureRecordId(activeSpec.id, TABLES.PLAN_SPEC))
432
- .content(toSpecData(activeSpec, { status: 'superseded' }))
433
- .output('after'),
434
- 'Failed to update superseded execution plan spec.',
435
- )
424
+ const updatedSpec = yield* tx
425
+ .update(ensureRecordId(activeSpec.id, TABLES.PLAN_SPEC))
426
+ .content(toSpecData(activeSpec, { status: 'superseded' }))
427
+ .output('after')
428
+ .pipe(
429
+ Effect.mapError((cause) =>
430
+ toExecutionPlanServiceError('Failed to update superseded execution plan spec.', cause),
431
+ ),
432
+ )
436
433
  const supersededSpec = yield* parseRowEffect(PlanSpecSchema, updatedSpec, 'superseded plan spec')
437
434
 
438
- const updatedRun = yield* effectTryPromise(
439
- () =>
440
- tx
441
- .update(ensureRecordId(activeRun.id, TABLES.PLAN_RUN))
442
- .content(
443
- toRunData(activeRun, {
444
- status: 'aborted',
445
- currentNodeId: null,
446
- waitingNodeId: null,
447
- readyNodeIds: [],
448
- completedAt: nowDate(),
449
- }),
450
- )
451
- .output('after'),
452
- 'Failed to abort previous execution run.',
453
- )
435
+ const updatedRun = yield* tx
436
+ .update(ensureRecordId(activeRun.id, TABLES.PLAN_RUN))
437
+ .content(
438
+ toRunData(activeRun, {
439
+ status: 'aborted',
440
+ currentNodeId: null,
441
+ waitingNodeId: null,
442
+ readyNodeIds: [],
443
+ completedAt: nowDate(),
444
+ }),
445
+ )
446
+ .output('after')
447
+ .pipe(
448
+ Effect.mapError((cause) => toExecutionPlanServiceError('Failed to abort previous execution run.', cause)),
449
+ )
454
450
  const abortedRun = yield* parseRowEffect(PlanRunSchema, updatedRun, 'aborted plan run')
455
451
 
456
- const createdSpec = yield* effectTryPromise(
457
- () =>
458
- tx
459
- .create(specId)
460
- .content(
461
- buildCompiledSpecCreateData({
462
- organizationId: params.organizationId,
463
- threadId: resolvedThreadId,
464
- leadAgentId: params.leadAgentId,
465
- compiled,
466
- version: supersededSpec.version + 1,
467
- replacedSpecId: supersededSpec.id,
468
- }),
469
- )
470
- .output('after'),
471
- 'Failed to create replacement execution plan spec.',
472
- )
473
- const spec = yield* parseRowEffect(PlanSpecSchema, createdSpec, 'replacement plan spec')
474
-
475
- yield* effectTryPromise(
476
- () =>
477
- createInitializedRunGraph({
478
- tx,
479
- runId,
480
- spec,
452
+ const createdSpec = yield* tx
453
+ .create(specId)
454
+ .content(
455
+ buildCompiledSpecCreateData({
481
456
  organizationId: params.organizationId,
482
457
  threadId: resolvedThreadId,
483
- sourceThreadId: activeRun.sourceThreadId,
484
458
  leadAgentId: params.leadAgentId,
485
- createdByAgentId: params.createdByAgentId ?? params.leadAgentId,
486
- requireApproval,
487
- nodes: compiled.nodes,
488
- emittedEvents,
489
- runPatch: { replacedRunId: abortedRun.id },
490
- createdEventType: 'plan-replaced',
491
- createdEventMessage: `Replaced execution plan "${activeSpec.title}" with "${spec.title}".`,
492
- createdEventDetail: {
493
- reason: params.input.reason,
494
- replacedRunId: recordIdToString(abortedRun.id, TABLES.PLAN_RUN),
495
- },
496
- checkpointReason: 'plan-replaced',
497
- planExecutor: deps.planExecutor,
459
+ compiled,
460
+ version: supersededSpec.version + 1,
461
+ replacedSpecId: supersededSpec.id,
498
462
  }),
499
- 'Failed to initialize replacement execution plan graph.',
463
+ )
464
+ .output('after')
465
+ .pipe(
466
+ Effect.mapError((cause) =>
467
+ toExecutionPlanServiceError('Failed to create replacement execution plan spec.', cause),
468
+ ),
469
+ )
470
+ const spec = yield* parseRowEffect(PlanSpecSchema, createdSpec, 'replacement plan spec')
471
+
472
+ yield* createInitializedRunGraph({
473
+ tx,
474
+ runId,
475
+ spec,
476
+ organizationId: params.organizationId,
477
+ threadId: resolvedThreadId,
478
+ sourceThreadId: activeRun.sourceThreadId,
479
+ leadAgentId: params.leadAgentId,
480
+ createdByAgentId: params.createdByAgentId ?? params.leadAgentId,
481
+ requireApproval,
482
+ nodes: compiled.nodes,
483
+ emittedEvents,
484
+ runPatch: { replacedRunId: abortedRun.id },
485
+ createdEventType: 'plan-replaced',
486
+ createdEventMessage: `Replaced execution plan "${activeSpec.title}" with "${spec.title}".`,
487
+ createdEventDetail: {
488
+ reason: params.input.reason,
489
+ replacedRunId: recordIdToString(abortedRun.id, TABLES.PLAN_RUN),
490
+ },
491
+ checkpointReason: 'plan-replaced',
492
+ planExecutor: deps.planExecutor,
493
+ }).pipe(
494
+ Effect.mapError((cause) =>
495
+ toExecutionPlanServiceError('Failed to initialize replacement execution plan graph.', cause),
496
+ ),
500
497
  )
501
498
  }),
502
499
  })
503
500
 
504
501
  if (!requireApproval) {
505
- yield* effectTryPromise(
506
- () =>
507
- attachPlanScheduleIfNeeded({
508
- db: databaseService,
509
- planRunService: deps.planRun,
510
- planSchedulerService: deps.planScheduler,
511
- organizationId: params.organizationId,
512
- threadId: resolvedThreadId,
513
- runId,
514
- planSpecId: specId,
515
- }),
516
- 'Failed to attach execution plan schedule.',
502
+ yield* attachPlanScheduleIfNeeded({
503
+ db: databaseService,
504
+ planRunService: deps.planRun,
505
+ planSchedulerService: deps.planScheduler,
506
+ organizationId: params.organizationId,
507
+ threadId: resolvedThreadId,
508
+ runId,
509
+ planSpecId: specId,
510
+ }).pipe(
511
+ Effect.mapError((cause) => toExecutionPlanServiceError('Failed to attach execution plan schedule.', cause)),
517
512
  )
518
513
  }
519
514
 
@@ -614,30 +609,25 @@ function approvePlanEffect(
614
609
  planEventDeliveryService: deps.planEventDelivery,
615
610
  run: (tx, emittedEvents) =>
616
611
  Effect.gen(function* () {
617
- const updatedRun = yield* effectTryPromise(
618
- () =>
619
- tx
620
- .update(ensureRecordId(run.id, TABLES.PLAN_RUN))
621
- .content(toRunData(run, { status: 'running', startedAt: run.startedAt ?? nowDate() }))
622
- .output('after'),
623
- 'Failed to activate execution run.',
624
- )
612
+ const updatedRun = yield* tx
613
+ .update(ensureRecordId(run.id, TABLES.PLAN_RUN))
614
+ .content(toRunData(run, { status: 'running', startedAt: run.startedAt ?? nowDate() }))
615
+ .output('after')
616
+ .pipe(Effect.mapError((cause) => toExecutionPlanServiceError('Failed to activate execution run.', cause)))
625
617
  const activatedRun = yield* parseRowEffect(PlanRunSchema, updatedRun, 'activated plan run')
626
618
 
627
- const synced = yield* effectTryPromise(
628
- () =>
629
- deps.planExecutor.syncRunGraph({
630
- tx,
631
- run: activatedRun,
632
- spec,
633
- nodeSpecs,
634
- nodeRuns,
635
- artifacts: [],
636
- emittedBy: params.emittedBy,
637
- capturedEvents: emittedEvents,
638
- }),
639
- 'Failed to sync execution run graph.',
640
- )
619
+ const synced = yield* deps.planExecutor
620
+ .syncRunGraph({
621
+ tx,
622
+ run: activatedRun,
623
+ spec,
624
+ nodeSpecs,
625
+ nodeRuns,
626
+ artifacts: [],
627
+ emittedBy: params.emittedBy,
628
+ capturedEvents: emittedEvents,
629
+ })
630
+ .pipe(Effect.mapError((cause) => toExecutionPlanServiceError('Failed to sync execution run graph.', cause)))
641
631
 
642
632
  yield* emitEvent({
643
633
  tx,
@@ -666,19 +656,15 @@ function approvePlanEffect(
666
656
  }),
667
657
  })
668
658
 
669
- yield* effectTryPromise(
670
- () =>
671
- attachPlanScheduleIfNeeded({
672
- db: databaseService,
673
- planRunService: deps.planRun,
674
- planSchedulerService: deps.planScheduler,
675
- organizationId: run.organizationId,
676
- threadId: run.threadId,
677
- runId: run.id,
678
- planSpecId: run.planSpecId,
679
- }),
680
- 'Failed to attach execution plan schedule.',
681
- )
659
+ yield* attachPlanScheduleIfNeeded({
660
+ db: databaseService,
661
+ planRunService: deps.planRun,
662
+ planSchedulerService: deps.planScheduler,
663
+ organizationId: run.organizationId,
664
+ threadId: run.threadId,
665
+ runId: run.id,
666
+ planSpecId: run.planSpecId,
667
+ }).pipe(Effect.mapError((cause) => toExecutionPlanServiceError('Failed to attach execution plan schedule.', cause)))
682
668
 
683
669
  return yield* finalizePlanSnapshotEffect(deps, { runId: run.id, emittedBy: params.emittedBy })
684
670
  })
@@ -726,22 +712,19 @@ function rejectPlanEffect(
726
712
  planEventDeliveryService: deps.planEventDelivery,
727
713
  run: (tx, emittedEvents) =>
728
714
  Effect.gen(function* () {
729
- const updatedRun = yield* effectTryPromise(
730
- () =>
731
- tx
732
- .update(ensureRecordId(run.id, TABLES.PLAN_RUN))
733
- .content(
734
- toRunData(run, {
735
- status: 'aborted',
736
- currentNodeId: null,
737
- waitingNodeId: null,
738
- readyNodeIds: [],
739
- completedAt: nowDate(),
740
- }),
741
- )
742
- .output('after'),
743
- 'Failed to abort execution run.',
744
- )
715
+ const updatedRun = yield* tx
716
+ .update(ensureRecordId(run.id, TABLES.PLAN_RUN))
717
+ .content(
718
+ toRunData(run, {
719
+ status: 'aborted',
720
+ currentNodeId: null,
721
+ waitingNodeId: null,
722
+ readyNodeIds: [],
723
+ completedAt: nowDate(),
724
+ }),
725
+ )
726
+ .output('after')
727
+ .pipe(Effect.mapError((cause) => toExecutionPlanServiceError('Failed to abort execution run.', cause)))
745
728
  const rejectedRun = yield* parseRowEffect(PlanRunSchema, updatedRun, 'rejected plan run')
746
729
 
747
730
  yield* emitEvent({
@@ -3,14 +3,15 @@ import { Context, Schema, Effect, Layer } from 'effect'
3
3
 
4
4
  import { ensureRecordId, recordIdToString } from '../db/record-id'
5
5
  import { TABLES } from '../db/tables'
6
+ import { ERROR_TAGS } from '../effect/errors'
6
7
  import { toIsoDateTimeString, unsafeDateFrom } from '../utils/date-time'
7
8
  import type { makePlanRunService } from './plan/plan-run.service'
8
9
  import { PlanRunServiceTag } from './plan/plan-run.service'
9
10
 
10
- class FeedbackLoopServiceError extends Schema.TaggedErrorClass<FeedbackLoopServiceError>()('FeedbackLoopServiceError', {
11
- message: Schema.String,
12
- cause: Schema.Defect,
13
- }) {}
11
+ class FeedbackLoopServiceError extends Schema.TaggedErrorClass<FeedbackLoopServiceError>()(
12
+ ERROR_TAGS.FeedbackLoopServiceError,
13
+ { message: Schema.String, cause: Schema.Defect },
14
+ ) {}
14
15
 
15
16
  export function makeFeedbackLoopService(planRunService: ReturnType<typeof makePlanRunService>) {
16
17
  return {