@lota-sdk/core 0.4.7 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -4,35 +4,62 @@ import type {
4
4
  PlanArtifactRecord,
5
5
  PlanArtifactSubmission,
6
6
  PlanFailureClass,
7
- PlanNodeResultSubmission,
8
7
  PlanNodeRunRecord,
9
8
  PlanNodeSpec,
10
9
  PlanNodeSpecRecord,
11
10
  PlanRunRecord,
12
11
  PlanSchemaRegistry,
13
12
  PlanSpecRecord,
13
+ PlanNodeOwner,
14
14
  PlanDraft,
15
- SerializableExecutionPlan,
16
15
  UpstreamHandoff,
17
16
  } from '@lota-sdk/shared'
17
+ import { Cause, Context, Effect, Layer, Match } from 'effect'
18
18
 
19
- import { agentRoster } from '../config/agent-defaults'
19
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
20
20
  import type { RecordIdInput } from '../db/record-id'
21
21
  import { ensureRecordId, recordIdToString } from '../db/record-id'
22
- import { databaseService } from '../db/service'
22
+ import type { SurrealDBService } from '../db/service'
23
23
  import { TABLES } from '../db/tables'
24
+ import { BadRequestError, ConfigurationError, DatabaseError } from '../effect/errors'
25
+ import { isPromiseLike } from '../effect/helpers'
26
+ import { AgentConfigServiceTag, DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
24
27
  import { resolvePlanNodeExecutionVisibility, shouldPlanNodeUseVisibleTurn } from '../runtime/execution-plan-visibility'
25
- import { getRuntimeAdapters } from '../runtime/runtime-extensions'
26
- import { agentExecutorService } from './agent-executor.service'
27
- import { monitoringWindowService } from './monitoring-window.service'
28
- import { planExecutorService } from './plan-executor.service'
29
- import { planRunService } from './plan-run.service'
30
- import type { PlanValidationIssueInput } from './plan-validator.service'
31
- import { pluginExecutorService } from './plugin-executor.service'
32
- import { skillResolverService } from './skill-resolver.service'
33
- import { systemExecutorService } from './system-executor.service'
34
- import { ThreadSchema } from './thread.types'
35
- import { userService } from './user.service'
28
+ import type { LotaRuntimeAdapters } from '../runtime/runtime-extensions'
29
+ import type { makeAgentExecutorService } from './agent-executor.service'
30
+ import { AgentExecutorServiceTag } from './agent-executor.service'
31
+ import { routeGraphFullEffect } from './graph-full-routing'
32
+ import type { makeMonitoringWindowService } from './monitoring-window.service'
33
+ import { MonitoringWindowServiceTag } from './monitoring-window.service'
34
+ import type { makePlanExecutorService } from './plan/plan-executor.service'
35
+ import { PlanExecutorServiceTag } from './plan/plan-executor.service'
36
+ import { serializeRunFull } from './plan/plan-run-serialization'
37
+ import type { makePlanRunService } from './plan/plan-run.service'
38
+ import { PlanRunServiceTag } from './plan/plan-run.service'
39
+ import type { PlanValidationIssueInput } from './plan/plan-validator.service'
40
+ import type { makePluginExecutorService } from './plugin-executor.service'
41
+ import { PluginExecutorServiceTag } from './plugin-executor.service'
42
+ import type { SkillResolverService } from './skill-resolver.service'
43
+ import { SkillResolverServiceTag } from './skill-resolver.service'
44
+ import type { makeSystemExecutorService } from './system-executor.service'
45
+ import { SystemExecutorServiceTag } from './system-executor.service'
46
+ import { ThreadSchema } from './thread/thread.types'
47
+ import type { makeUserService } from './user.service'
48
+ import { UserServiceTag } from './user.service'
49
+
50
+ interface OwnershipDispatcherDeps {
51
+ db: SurrealDBService
52
+ agentConfig: ResolvedAgentConfig
53
+ runtimeAdapters: LotaRuntimeAdapters
54
+ agentExecutor: ReturnType<typeof makeAgentExecutorService>
55
+ monitoringWindow: ReturnType<typeof makeMonitoringWindowService>
56
+ planExecutor: ReturnType<typeof makePlanExecutorService>
57
+ planRun: ReturnType<typeof makePlanRunService>
58
+ pluginExecutor: ReturnType<typeof makePluginExecutorService>
59
+ skillResolver: SkillResolverService
60
+ systemExecutor: ReturnType<typeof makeSystemExecutorService>
61
+ user: ReturnType<typeof makeUserService>
62
+ }
36
63
 
37
64
  const STABLE_RUN_STATUSES = new Set(['pending-approval', 'awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
38
65
  const MAX_DISPATCH_ITERATIONS = 64
@@ -101,204 +128,89 @@ function formatDispatchError(error: unknown): string {
101
128
  return error instanceof Error ? error.message : String(error)
102
129
  }
103
130
 
104
- class OwnershipDispatcherService {
105
- validateDraftExecutors(draft: PlanDraft): PlanValidationIssueInput[] {
106
- const issues: PlanValidationIssueInput[] = []
107
-
108
- for (const node of draft.nodes) {
109
- if (node.owner.executorType === 'agent') {
110
- if (!agentRoster.includes(node.owner.ref)) {
111
- issues.push({
112
- severity: 'blocking',
113
- code: 'agent_executor_missing',
114
- message: `Node "${node.label}" references unknown agent executor "${node.owner.ref}".`,
115
- nodeId: node.id,
116
- detail: { agentId: node.owner.ref },
117
- })
118
- }
119
- continue
120
- }
121
-
122
- if (node.owner.executorType === 'plugin') {
123
- issues.push(...pluginExecutorService.validateOwner(node.owner, node.id))
124
- continue
125
- }
126
-
127
- if (node.owner.executorType === 'system') {
128
- issues.push(...systemExecutorService.validateOwner(node.owner, node.id))
129
- continue
130
- }
131
-
132
- if (node.owner.executorType === 'skill') {
133
- // Skill owners are validated at execution time via skillResolverService.
134
- }
135
- }
136
-
137
- return issues
138
- }
139
-
140
- async dispatchRunToStableBoundary(params: {
141
- runId: RecordIdInput
142
- emittedBy: string
143
- }): Promise<SerializableExecutionPlan> {
144
- const initialRun = await planRunService.getRunById(params.runId)
145
- const autoDispatchEnabled = await this.shouldAutoDispatch(initialRun)
146
- if (!autoDispatchEnabled) {
147
- return this.serializeRun(initialRun.id)
148
- }
149
-
150
- let iteration = 0
151
- while (iteration < MAX_DISPATCH_ITERATIONS) {
152
- const run = await planRunService.getRunById(params.runId)
153
- if (STABLE_RUN_STATUSES.has(run.status) || run.status !== 'running' || !run.currentNodeId) {
154
- return this.serializeRun(run.id)
155
- }
156
-
157
- const spec = await planRunService.getPlanSpecById(run.planSpecId)
158
-
159
- if (spec.executionMode === 'graph-full') {
160
- const { globalOrchestratorService } = await import('./global-orchestrator.service')
161
- await globalOrchestratorService.routeGraphFull({
162
- threadId: recordIdToString(run.threadId, TABLES.THREAD),
163
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
164
- })
165
- return this.serializeRun(run.id)
166
- }
167
-
168
- const nodeSpecRecord = await planRunService.getNodeSpecByNodeId(spec.id, run.currentNodeId)
169
- const planNode = toPlanNodeSpec(nodeSpecRecord)
170
- if (planNode.owner.executorType === 'user') {
171
- return this.serializeRun(run.id)
172
- }
173
-
174
- const nodeRun = await planRunService.getNodeRunByNodeId(run.id, nodeSpecRecord.nodeId)
175
- if (nodeRun.status === 'monitoring') {
176
- // Monitoring nodes are managed by the scheduler — treat as stable
177
- return this.serializeRun(run.id)
178
- }
179
- if (nodeRun.status !== 'running') {
180
- return this.serializeRun(run.id)
181
- }
182
- if (shouldPlanNodeUseVisibleTurn(spec, nodeSpecRecord)) {
183
- return this.serializeRun(run.id)
184
- }
185
-
186
- const [artifacts, dispatchContext] = await Promise.all([
187
- planRunService.listArtifacts(run.id),
188
- this.buildDispatchContext(run, spec, nodeSpecRecord),
189
- ])
190
- const inputArtifacts = artifacts
191
- .filter((artifact) => nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
192
- .map((artifact) => toArtifactSubmission(artifact))
193
-
194
- try {
195
- const result = await this.dispatchNode({
196
- nodeSpec: planNode,
197
- resolvedInput: nodeRun.resolvedInput ?? {},
198
- inputArtifacts,
199
- context: { ...dispatchContext, nodeId: planNode.id },
200
- executionMode: spec.executionMode,
201
- schemaRegistry: spec.schemaRegistry,
202
- })
131
+ function toDispatchDatabaseError(message: string, cause: unknown) {
132
+ return new DatabaseError({ message, cause })
133
+ }
203
134
 
204
- await planExecutorService.submitNodeResult({
205
- threadId: run.threadId,
206
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
207
- nodeId: planNode.id,
208
- emittedBy: planNode.owner.ref,
209
- result,
210
- })
211
- } catch (error) {
212
- await planExecutorService.blockNodeOnDispatchFailure({
213
- threadId: run.threadId,
214
- runId: recordIdToString(run.id, TABLES.PLAN_RUN),
215
- nodeId: planNode.id,
216
- emittedBy: planNode.owner.ref,
217
- message: formatDispatchError(error),
218
- failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error }),
219
- })
220
- return await this.serializeRun(run.id)
221
- }
135
+ const matchDraftExecutor = (deps: OwnershipDispatcherDeps, node: { id: string; label: string }) =>
136
+ Match.type<PlanNodeOwner>().pipe(
137
+ Match.discriminator('executorType')('agent', (owner): PlanValidationIssueInput[] =>
138
+ deps.agentConfig.roster.includes(owner.ref)
139
+ ? []
140
+ : [
141
+ {
142
+ severity: 'blocking',
143
+ code: 'agent_executor_missing',
144
+ message: `Node "${node.label}" references unknown agent executor "${owner.ref}".`,
145
+ nodeId: node.id,
146
+ detail: { agentId: owner.ref },
147
+ },
148
+ ],
149
+ ),
150
+ Match.discriminator('executorType')('plugin', (owner): PlanValidationIssueInput[] =>
151
+ deps.pluginExecutor.validateOwner(owner, node.id),
152
+ ),
153
+ Match.discriminator('executorType')('system', (owner): PlanValidationIssueInput[] =>
154
+ deps.systemExecutor.validateOwner(owner, node.id),
155
+ ),
156
+ Match.discriminator('executorType')('skill', (): PlanValidationIssueInput[] => []),
157
+ Match.discriminator('executorType')('user', (): PlanValidationIssueInput[] => []),
158
+ Match.exhaustive,
159
+ )
160
+
161
+ function validateDraftExecutors(deps: OwnershipDispatcherDeps, draft: PlanDraft): PlanValidationIssueInput[] {
162
+ return draft.nodes.flatMap((node) => matchDraftExecutor(deps, node)(node.owner))
163
+ }
222
164
 
223
- iteration += 1
165
+ const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRecord) =>
166
+ Effect.gen(function* () {
167
+ const workspaceProvider = deps.runtimeAdapters.workspaceProvider
168
+ if (!workspaceProvider) {
169
+ return true
224
170
  }
225
171
 
226
- throw new Error(
227
- `Ownership dispatch exceeded ${MAX_DISPATCH_ITERATIONS} iterations for run ${recordIdToString(
228
- ensureRecordId(params.runId, TABLES.PLAN_RUN),
229
- TABLES.PLAN_RUN,
230
- )}.`,
172
+ const workspace = yield* Effect.tryPromise(() =>
173
+ workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
231
174
  )
232
- }
233
-
234
- async dispatchReadyNode(params: {
235
- run: PlanRunRecord
236
- nodeSpecRecord: PlanNodeSpecRecord
237
- nodeRun: PlanNodeRunRecord
238
- spec: PlanSpecRecord
239
- executionModeOverride?: ExecutionMode
240
- }): Promise<PlanNodeResultSubmission> {
241
- if (shouldPlanNodeUseVisibleTurn(params.spec, params.nodeSpecRecord)) {
242
- throw new Error(
243
- `Node "${params.nodeSpecRecord.nodeId}" requires a visible plan turn and cannot be silently dispatched.`,
244
- )
245
- }
246
-
247
- const planNode = toPlanNodeSpec(params.nodeSpecRecord)
248
- const [artifacts, dispatchContext] = await Promise.all([
249
- planRunService.listArtifacts(params.run.id),
250
- this.buildDispatchContext(params.run, params.spec, params.nodeSpecRecord),
251
- ])
252
- const inputArtifacts = artifacts
253
- .filter((artifact) => params.nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
254
- .map((artifact) => toArtifactSubmission(artifact))
255
-
256
- return this.dispatchNode({
257
- nodeSpec: planNode,
258
- resolvedInput: params.nodeRun.resolvedInput ?? {},
259
- inputArtifacts,
260
- context: { ...dispatchContext, nodeId: planNode.id },
261
- executionMode: params.spec.executionMode,
262
- executionModeOverride: params.executionModeOverride,
263
- schemaRegistry: params.spec.schemaRegistry,
264
- })
265
- }
266
-
267
- private async shouldAutoDispatch(run: PlanRunRecord): Promise<boolean> {
268
- const workspaceProvider = getRuntimeAdapters().workspaceProvider
269
- if (!workspaceProvider) {
175
+ if (!workspaceProvider.getLifecycleState) {
270
176
  return true
271
177
  }
272
178
 
273
- const workspace = await workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION))
274
- const lifecycleState = await workspaceProvider.getLifecycleState?.(workspace)
275
- return lifecycleState?.bootstrapActive !== true
276
- }
277
-
278
- resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
279
- return resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpecRecord)
280
- }
179
+ const lifecycleState = yield* Effect.gen(function* () {
180
+ const result = workspaceProvider.getLifecycleState?.call(workspaceProvider, workspace)
181
+ if (isPromiseLike(result)) {
182
+ return yield* Effect.tryPromise(() => result)
183
+ }
281
184
 
282
- private async buildDispatchContext(
283
- run: PlanRunRecord,
284
- spec: PlanSpecRecord,
285
- nodeSpecRecord: PlanNodeSpecRecord,
286
- ): Promise<Omit<OwnershipDispatchContext, 'nodeId'>> {
185
+ return result
186
+ })
187
+ return lifecycleState ? lifecycleState.bootstrapActive !== true : true
188
+ })
189
+
190
+ const buildDispatchContextEffect = (
191
+ deps: OwnershipDispatcherDeps,
192
+ run: PlanRunRecord,
193
+ spec: PlanSpecRecord,
194
+ nodeSpecRecord: PlanNodeSpecRecord,
195
+ ) =>
196
+ Effect.gen(function* () {
287
197
  const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
288
198
  const threadId = recordIdToString(run.threadId, TABLES.THREAD)
289
199
  const planId = recordIdToString(run.id, TABLES.PLAN_RUN)
290
- const [thread, nodeSpecs, nodeRuns] = await Promise.all([
291
- databaseService.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema),
292
- planRunService.listNodeSpecs(spec.id),
293
- planRunService.listNodeRuns(run.id),
200
+ const [thread, nodeSpecs, nodeRuns] = yield* Effect.all([
201
+ deps.db
202
+ .findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema)
203
+ .pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load thread context.', cause))),
204
+ deps.planRun
205
+ .listNodeSpecs(spec.id)
206
+ .pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load plan node specs.', cause))),
207
+ deps.planRun
208
+ .listNodeRuns(run.id)
209
+ .pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load plan node runs.', cause))),
294
210
  ])
295
211
  const userId = thread?.userId ? recordIdToString(thread.userId, TABLES.USER) : undefined
296
- const userName = userId
297
- ? await userService
298
- .getUser(userId)
299
- .then((user) => user.name)
300
- .catch(() => undefined)
301
- : undefined
212
+ const userResult = userId ? yield* Effect.exit(deps.user.getUser(userId)) : undefined
213
+ const userName = userResult?._tag === 'Success' ? userResult.value.name : undefined
302
214
  const nodeSpecsById = new Map(nodeSpecs.map((candidate) => [candidate.nodeId, candidate]))
303
215
  const upstreamHandoffs: UpstreamHandoff[] = nodeRuns
304
216
  .filter(
@@ -324,19 +236,17 @@ class OwnershipDispatcherService {
324
236
  ...(userName ? { userName } : {}),
325
237
  ...(upstreamHandoffs.length > 0 ? { upstreamHandoffs } : {}),
326
238
  }
327
- }
239
+ })
328
240
 
329
- private async serializeRun(runId: RecordIdInput): Promise<SerializableExecutionPlan> {
330
- return planRunService.toSerializablePlan(await planRunService.getRunById(runId), {
331
- includeEvents: true,
332
- includeArtifacts: true,
333
- includeApprovals: true,
334
- includeCheckpoints: true,
335
- includeValidationIssues: true,
336
- })
337
- }
241
+ const serializeRunEffect = (deps: OwnershipDispatcherDeps, runId: RecordIdInput) =>
242
+ Effect.gen(function* () {
243
+ const run = yield* deps.planRun.getRunById(runId)
244
+ return yield* serializeRunFull(deps.planRun, run)
245
+ })
338
246
 
339
- async dispatchNode(params: {
247
+ const dispatchNodeEffect = (
248
+ deps: OwnershipDispatcherDeps,
249
+ params: {
340
250
  nodeSpec: PlanNodeSpec
341
251
  resolvedInput: Record<string, unknown>
342
252
  inputArtifacts: PlanArtifactSubmission[]
@@ -344,83 +254,324 @@ class OwnershipDispatcherService {
344
254
  executionMode?: ExecutionMode
345
255
  executionModeOverride?: ExecutionMode
346
256
  schemaRegistry?: PlanSchemaRegistry
347
- }) {
257
+ },
258
+ ) =>
259
+ Effect.gen(function* () {
348
260
  const effectiveExecutionMode = params.executionModeOverride ?? params.executionMode
349
261
  if (params.nodeSpec.type === 'monitoring' && params.nodeSpec.monitoringConfig) {
350
- await monitoringWindowService.startMonitoringWindow({
262
+ const monitoringConfig = params.nodeSpec.monitoringConfig
263
+ yield* deps.monitoringWindow.startMonitoringWindow({
351
264
  runId: params.context.planId,
352
265
  nodeId: params.nodeSpec.id,
353
- config: params.nodeSpec.monitoringConfig,
266
+ config: monitoringConfig,
354
267
  organizationId: params.context.organizationId,
355
268
  threadId: params.context.threadId,
356
269
  })
357
270
  return {
358
271
  notes: `Monitoring window started for node "${params.nodeSpec.label}".`,
359
- structuredOutput: { status: 'monitoring-started', config: params.nodeSpec.monitoringConfig },
272
+ structuredOutput: { status: 'monitoring-started', config: monitoringConfig },
360
273
  artifacts: [],
361
274
  }
362
275
  }
363
276
 
364
- if (params.nodeSpec.owner.executorType === 'agent') {
365
- return agentExecutorService.executeNode({
366
- nodeSpec: params.nodeSpec,
367
- resolvedInput: params.resolvedInput,
368
- inputArtifacts: params.inputArtifacts,
369
- context: params.context,
370
- executionMode: effectiveExecutionMode,
371
- schemaRegistry: params.schemaRegistry,
372
- })
373
- }
374
- if (params.nodeSpec.owner.executorType === 'plugin') {
375
- return pluginExecutorService.executeNode({
376
- nodeSpec: params.nodeSpec,
377
- resolvedInput: params.resolvedInput,
378
- context: params.context,
379
- })
380
- }
381
- if (params.nodeSpec.owner.executorType === 'system') {
382
- return systemExecutorService.executeNode({
383
- nodeSpec: params.nodeSpec,
384
- resolvedInput: params.resolvedInput,
385
- context: params.context,
386
- })
387
- }
388
- if (params.nodeSpec.owner.executorType === 'skill') {
389
- const resolved = await skillResolverService.resolve({
390
- skillRef: params.nodeSpec.owner.ref,
391
- organizationId: params.context.organizationId,
392
- })
393
- if (!resolved) {
394
- throw new Error(`Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`)
395
- }
396
-
397
- if (resolved.executorType === 'agent') {
398
- const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
399
- return agentExecutorService.executeNode({
400
- nodeSpec: skillNodeSpec,
277
+ switch (params.nodeSpec.owner.executorType) {
278
+ case 'agent':
279
+ return yield* deps.agentExecutor.executeNode({
280
+ nodeSpec: params.nodeSpec,
401
281
  resolvedInput: params.resolvedInput,
402
282
  inputArtifacts: params.inputArtifacts,
403
283
  context: params.context,
404
284
  executionMode: effectiveExecutionMode,
405
285
  schemaRegistry: params.schemaRegistry,
406
286
  })
287
+ case 'plugin':
288
+ return yield* deps.pluginExecutor.executeNode({
289
+ nodeSpec: params.nodeSpec,
290
+ resolvedInput: params.resolvedInput,
291
+ context: params.context,
292
+ })
293
+ case 'system':
294
+ return yield* deps.systemExecutor.executeNode({
295
+ nodeSpec: params.nodeSpec,
296
+ resolvedInput: params.resolvedInput,
297
+ context: params.context,
298
+ })
299
+ case 'skill': {
300
+ const resolved = yield* deps.skillResolver.resolve({
301
+ skillRef: params.nodeSpec.owner.ref,
302
+ organizationId: params.context.organizationId,
303
+ })
304
+ if (!resolved) {
305
+ return yield* new ConfigurationError({
306
+ message: `Skill "${params.nodeSpec.owner.ref}" could not be resolved. This is a configuration error.`,
307
+ })
308
+ }
309
+
310
+ if (resolved.executorType === 'agent') {
311
+ const skillNodeSpec = { ...params.nodeSpec, owner: { executorType: 'agent' as const, ref: resolved.ref } }
312
+ return yield* deps.agentExecutor.executeNode({
313
+ nodeSpec: skillNodeSpec,
314
+ resolvedInput: params.resolvedInput,
315
+ inputArtifacts: params.inputArtifacts,
316
+ context: params.context,
317
+ executionMode: effectiveExecutionMode,
318
+ schemaRegistry: params.schemaRegistry,
319
+ })
320
+ }
321
+
322
+ return yield* deps.pluginExecutor.executeNode({
323
+ nodeSpec: {
324
+ ...params.nodeSpec,
325
+ owner: {
326
+ executorType: 'plugin' as const,
327
+ ref: resolved.ref,
328
+ operation: resolved.operation ?? params.nodeSpec.owner.ref,
329
+ },
330
+ },
331
+ resolvedInput: params.resolvedInput,
332
+ context: params.context,
333
+ })
334
+ }
335
+ case 'user':
336
+ return yield* new BadRequestError({
337
+ message: `User-owned node "${params.nodeSpec.id}" cannot be auto-dispatched.`,
338
+ })
339
+ }
340
+ })
341
+
342
+ const dispatchRunToStableBoundaryEffect = (
343
+ deps: OwnershipDispatcherDeps,
344
+ params: { runId: RecordIdInput; emittedBy: string },
345
+ ) =>
346
+ Effect.gen(function* () {
347
+ const initialRun = yield* deps.planRun.getRunById(params.runId)
348
+ const autoDispatchEnabled = yield* shouldAutoDispatchEffect(deps, initialRun)
349
+ if (!autoDispatchEnabled) {
350
+ return yield* serializeRunEffect(deps, initialRun.id)
351
+ }
352
+
353
+ for (let iteration = 0; iteration < MAX_DISPATCH_ITERATIONS; iteration += 1) {
354
+ const run = yield* deps.planRun.getRunById(params.runId)
355
+ if (STABLE_RUN_STATUSES.has(run.status) || run.status !== 'running' || !run.currentNodeId) {
356
+ return yield* serializeRunEffect(deps, run.id)
407
357
  }
408
- return pluginExecutorService.executeNode({
409
- nodeSpec: {
410
- ...params.nodeSpec,
411
- owner: {
412
- executorType: 'plugin' as const,
413
- ref: resolved.ref,
414
- operation: resolved.operation ?? params.nodeSpec.owner.ref,
358
+
359
+ const spec = yield* deps.planRun.getPlanSpecById(run.planSpecId)
360
+
361
+ if (spec.executionMode === 'graph-full') {
362
+ yield* routeGraphFullEffect(
363
+ { threadId: recordIdToString(run.threadId, TABLES.THREAD), runId: recordIdToString(run.id, TABLES.PLAN_RUN) },
364
+ {
365
+ dispatchReadyNode: (dispatchParams) => dispatchReadyNodeEffect(deps, dispatchParams),
366
+ planExecutorService: deps.planExecutor,
367
+ planRunService: deps.planRun,
415
368
  },
416
- },
417
- resolvedInput: params.resolvedInput,
418
- context: params.context,
369
+ ).pipe(
370
+ Effect.catch(() =>
371
+ Effect.fail(
372
+ new ConfigurationError({
373
+ message: `Failed to route graph-full execution for run ${recordIdToString(run.id, TABLES.PLAN_RUN)}.`,
374
+ }),
375
+ ),
376
+ ),
377
+ )
378
+ return yield* serializeRunEffect(deps, run.id)
379
+ }
380
+
381
+ const currentNodeId = run.currentNodeId
382
+ if (!currentNodeId) {
383
+ return yield* serializeRunEffect(deps, run.id)
384
+ }
385
+
386
+ const nodeSpecRecord = yield* deps.planRun.getNodeSpecByNodeId(spec.id, currentNodeId)
387
+ const planNode = toPlanNodeSpec(nodeSpecRecord)
388
+ if (planNode.owner.executorType === 'user') {
389
+ return yield* serializeRunEffect(deps, run.id)
390
+ }
391
+
392
+ const nodeId = nodeSpecRecord.nodeId
393
+ if (!nodeId) {
394
+ return yield* new ConfigurationError({
395
+ message: `Node spec "${nodeSpecRecord.label}" is missing an executable node id.`,
396
+ })
397
+ }
398
+
399
+ const nodeRun = yield* deps.planRun.getNodeRunByNodeId(run.id, nodeId)
400
+ if (nodeRun.status === 'monitoring') {
401
+ return yield* serializeRunEffect(deps, run.id)
402
+ }
403
+ if (nodeRun.status !== 'running') {
404
+ return yield* serializeRunEffect(deps, run.id)
405
+ }
406
+ if (shouldPlanNodeUseVisibleTurn(spec, nodeSpecRecord)) {
407
+ return yield* serializeRunEffect(deps, run.id)
408
+ }
409
+
410
+ const [artifacts, dispatchContext] = yield* Effect.all([
411
+ deps.planRun
412
+ .listArtifacts(run.id)
413
+ .pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load run artifacts.', cause))),
414
+ buildDispatchContextEffect(deps, run, spec, nodeSpecRecord),
415
+ ])
416
+ const inputArtifacts = artifacts
417
+ .filter((artifact) => nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
418
+ .map((artifact) => toArtifactSubmission(artifact))
419
+
420
+ const dispatchExit = yield* Effect.exit(
421
+ Effect.gen(function* () {
422
+ const result = yield* dispatchNodeEffect(deps, {
423
+ nodeSpec: planNode,
424
+ resolvedInput: nodeRun.resolvedInput ?? {},
425
+ inputArtifacts,
426
+ context: { ...dispatchContext, nodeId: planNode.id },
427
+ executionMode: spec.executionMode,
428
+ schemaRegistry: spec.schemaRegistry,
429
+ })
430
+
431
+ yield* Effect.tryPromise(() =>
432
+ deps.planExecutor.submitNodeResult({
433
+ threadId: run.threadId,
434
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
435
+ nodeId: planNode.id,
436
+ emittedBy: planNode.owner.ref,
437
+ result,
438
+ }),
439
+ )
440
+ }),
441
+ )
442
+
443
+ if (dispatchExit._tag === 'Failure') {
444
+ const failure = Cause.squash(dispatchExit.cause)
445
+ yield* Effect.tryPromise(() =>
446
+ deps.planExecutor.blockNodeOnDispatchFailure({
447
+ threadId: run.threadId,
448
+ runId: recordIdToString(run.id, TABLES.PLAN_RUN),
449
+ nodeId: planNode.id,
450
+ emittedBy: planNode.owner.ref,
451
+ message: formatDispatchError(failure),
452
+ failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error: failure }),
453
+ }),
454
+ )
455
+ return yield* serializeRunEffect(deps, run.id)
456
+ }
457
+ }
458
+
459
+ return yield* new ConfigurationError({
460
+ message: `Ownership dispatch exceeded ${MAX_DISPATCH_ITERATIONS} iterations for run ${recordIdToString(
461
+ ensureRecordId(params.runId, TABLES.PLAN_RUN),
462
+ TABLES.PLAN_RUN,
463
+ )}.`,
464
+ })
465
+ })
466
+
467
+ const dispatchReadyNodeEffect = (
468
+ deps: OwnershipDispatcherDeps,
469
+ params: {
470
+ run: PlanRunRecord
471
+ nodeSpecRecord: PlanNodeSpecRecord
472
+ nodeRun: PlanNodeRunRecord
473
+ spec: PlanSpecRecord
474
+ executionModeOverride?: ExecutionMode
475
+ },
476
+ ) =>
477
+ Effect.gen(function* () {
478
+ if (shouldPlanNodeUseVisibleTurn(params.spec, params.nodeSpecRecord)) {
479
+ return yield* new BadRequestError({
480
+ message: `Node "${params.nodeSpecRecord.nodeId}" requires a visible plan turn and cannot be silently dispatched.`,
419
481
  })
420
482
  }
421
483
 
422
- throw new Error(`User-owned node "${params.nodeSpec.id}" cannot be auto-dispatched.`)
423
- }
484
+ const planNode = toPlanNodeSpec(params.nodeSpecRecord)
485
+ const [artifacts, dispatchContext] = yield* Effect.all([
486
+ deps.planRun
487
+ .listArtifacts(params.run.id)
488
+ .pipe(Effect.mapError((cause) => toDispatchDatabaseError('Failed to load run artifacts.', cause))),
489
+ buildDispatchContextEffect(deps, params.run, params.spec, params.nodeSpecRecord),
490
+ ])
491
+ const inputArtifacts = artifacts
492
+ .filter((artifact) => params.nodeSpecRecord.upstreamNodeIds.includes(artifact.nodeId))
493
+ .map((artifact) => toArtifactSubmission(artifact))
494
+
495
+ return yield* dispatchNodeEffect(deps, {
496
+ nodeSpec: planNode,
497
+ resolvedInput: params.nodeRun.resolvedInput ?? {},
498
+ inputArtifacts,
499
+ context: { ...dispatchContext, nodeId: planNode.id },
500
+ executionMode: params.spec.executionMode,
501
+ executionModeOverride: params.executionModeOverride,
502
+ schemaRegistry: params.spec.schemaRegistry,
503
+ })
504
+ })
505
+
506
+ function resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
507
+ return resolvePlanNodeExecutionVisibility(params.spec, params.nodeSpecRecord)
424
508
  }
425
509
 
426
- export const ownershipDispatcherService = new OwnershipDispatcherService()
510
+ export function makeOwnershipDispatcherService(deps: OwnershipDispatcherDeps) {
511
+ return {
512
+ validateDraftExecutors(draft: PlanDraft) {
513
+ return validateDraftExecutors(deps, draft)
514
+ },
515
+ dispatchRunToStableBoundary(params: { runId: RecordIdInput; emittedBy: string }) {
516
+ return dispatchRunToStableBoundaryEffect(deps, params)
517
+ },
518
+ dispatchReadyNode(params: {
519
+ run: PlanRunRecord
520
+ nodeSpecRecord: PlanNodeSpecRecord
521
+ nodeRun: PlanNodeRunRecord
522
+ spec: PlanSpecRecord
523
+ executionModeOverride?: ExecutionMode
524
+ }) {
525
+ return dispatchReadyNodeEffect(deps, params)
526
+ },
527
+ resolveExecutionVisibility(params: { spec: PlanSpecRecord; nodeSpecRecord: PlanNodeSpecRecord }) {
528
+ return resolveExecutionVisibility(params)
529
+ },
530
+ dispatchNode(params: {
531
+ nodeSpec: PlanNodeSpec
532
+ resolvedInput: Record<string, unknown>
533
+ inputArtifacts: PlanArtifactSubmission[]
534
+ context: OwnershipDispatchContext
535
+ executionMode?: ExecutionMode
536
+ executionModeOverride?: ExecutionMode
537
+ schemaRegistry?: PlanSchemaRegistry
538
+ }) {
539
+ return dispatchNodeEffect(deps, params)
540
+ },
541
+ } as const
542
+ }
543
+
544
+ export class OwnershipDispatcherServiceTag extends Context.Service<
545
+ OwnershipDispatcherServiceTag,
546
+ ReturnType<typeof makeOwnershipDispatcherService>
547
+ >()('OwnershipDispatcherService') {}
548
+
549
+ export const OwnershipDispatcherServiceLive = Layer.effect(
550
+ OwnershipDispatcherServiceTag,
551
+ Effect.gen(function* () {
552
+ const db = yield* DatabaseServiceTag
553
+ const agentConfig = yield* AgentConfigServiceTag
554
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
555
+ const planRun = yield* PlanRunServiceTag
556
+ const agentExecutor = yield* AgentExecutorServiceTag
557
+ const monitoringWindow = yield* MonitoringWindowServiceTag
558
+ const planExecutor = yield* PlanExecutorServiceTag
559
+ const pluginExecutor = yield* PluginExecutorServiceTag
560
+ const skillResolver = yield* SkillResolverServiceTag
561
+ const systemExecutor = yield* SystemExecutorServiceTag
562
+ const user = yield* UserServiceTag
563
+ return makeOwnershipDispatcherService({
564
+ db,
565
+ agentConfig,
566
+ runtimeAdapters,
567
+ agentExecutor,
568
+ monitoringWindow,
569
+ planExecutor,
570
+ planRun,
571
+ pluginExecutor,
572
+ skillResolver,
573
+ systemExecutor,
574
+ user,
575
+ })
576
+ }),
577
+ )