@lota-sdk/core 0.4.8 → 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/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  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
+ )