@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
@@ -9,12 +9,17 @@ import type {
9
9
  } from '@lota-sdk/shared'
10
10
  import { PlanNodeResultSubmissionSchema, WriteIntentSchema } from '@lota-sdk/shared'
11
11
  import { stepCountIs, tool } from 'ai'
12
- import type { ToolLoopAgent, ToolSet } from 'ai'
12
+ import type { ToolSet } from 'ai'
13
+ import { Context, Schema, Effect, Layer } from 'effect'
13
14
 
14
- import { agentRoster, buildAgentTools, createAgent, getAgentRuntimeConfig } from '../config/agent-defaults'
15
+ import { getAgentRoster, getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../config/agent-defaults'
15
16
  import { ensureRecordId } from '../db/record-id'
16
- import { databaseService } from '../db/service'
17
+ import type { SurrealDBService } from '../db/service'
17
18
  import { TABLES } from '../db/tables'
19
+ import { BadRequestError, NotFoundError } from '../effect/errors'
20
+ import { runPromise } from '../effect/runtime'
21
+ import { DatabaseServiceTag } from '../effect/services'
22
+ import { toValidationError } from '../effect/zod'
18
23
  import {
19
24
  OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES,
20
25
  buildCompletionCheckStructuredOutputHints,
@@ -22,13 +27,24 @@ import {
22
27
  buildOwnershipDispatchResponseGuard,
23
28
  } from '../runtime/agent-runtime-policy'
24
29
  import { mergeInstructionSections } from '../runtime/instruction-sections'
25
- import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
30
+ import {
31
+ buildIndexedRepositoriesContext,
32
+ getGithubInstallationForOrganization,
33
+ getLinearInstallationByOrgId,
34
+ } from '../runtime/plugin-resolution'
26
35
  import { getTurnHooks } from '../runtime/runtime-extensions'
27
- import { asRecord, readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
28
- import { nodeWorkspaceService } from './node-workspace.service'
29
- import type { PlanValidationIssueInput } from './plan-validator.service'
30
- import { ThreadSchema } from './thread.types'
31
- import { writeIntentValidatorService } from './write-intent-validator.service'
36
+ import { readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
37
+ import { nowDate } from '../utils/date-time'
38
+ import type { makeNodeWorkspaceService } from './node-workspace.service'
39
+ import { NodeWorkspaceServiceTag } from './node-workspace.service'
40
+ import type { PlanValidationIssueInput } from './plan/plan-validator.service'
41
+ import { ThreadSchema } from './thread/thread.types'
42
+ import type { makeWriteIntentValidatorService } from './write-intent-validator.service'
43
+ import { WriteIntentValidatorServiceTag } from './write-intent-validator.service'
44
+
45
+ function isRecord(value: unknown): value is Record<string, unknown> {
46
+ return typeof value === 'object' && value !== null
47
+ }
32
48
 
33
49
  function applyToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpec): ToolSet {
34
50
  const blockedToolNames = new Set([...OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES, ...nodeSpec.toolPolicy.deny])
@@ -74,248 +90,344 @@ export function buildWriteIntentDispatchPrompt(nodeSpec: PlanNodeSpec): string {
74
90
 
75
91
  const MAX_SELF_CORRECTION_RETRIES = 3
76
92
 
77
- class AgentExecutorService {
78
- validateOwner(agentId: string, nodeId: string): PlanValidationIssueInput[] {
79
- if (!agentRoster.includes(agentId)) {
80
- return [
81
- {
82
- severity: 'blocking',
83
- code: 'agent_executor_missing',
84
- message: `Node "${nodeId}" references unknown agent executor "${agentId}".`,
85
- nodeId,
86
- detail: { agentId },
87
- },
88
- ]
93
+ function validateOwner(agentId: string, nodeId: string): PlanValidationIssueInput[] {
94
+ if (!getAgentRoster().includes(agentId)) {
95
+ return [
96
+ {
97
+ severity: 'blocking',
98
+ code: 'agent_executor_missing',
99
+ message: `Node "${nodeId}" references unknown agent executor "${agentId}".`,
100
+ nodeId,
101
+ detail: { agentId },
102
+ },
103
+ ]
104
+ }
105
+
106
+ return []
107
+ }
108
+
109
+ class AgentExecutorError extends Schema.TaggedErrorClass<AgentExecutorError>()('AgentExecutorError', {
110
+ operation: Schema.String,
111
+ message: Schema.String,
112
+ cause: Schema.Defect,
113
+ }) {}
114
+
115
+ function tryAgentExecutorPromise<A>(
116
+ operation: string,
117
+ message: string,
118
+ thunk: () => PromiseLike<A> | A | Effect.Effect<A, unknown, never>,
119
+ ): Effect.Effect<A, AgentExecutorError> {
120
+ return Effect.suspend(() => {
121
+ try {
122
+ const value = thunk()
123
+ if (Effect.isEffect(value)) {
124
+ return value.pipe(Effect.mapError((cause) => new AgentExecutorError({ operation, message, cause })))
125
+ }
126
+
127
+ return Effect.tryPromise({
128
+ try: () => Promise.resolve(value),
129
+ catch: (cause) => new AgentExecutorError({ operation, message, cause }),
130
+ })
131
+ } catch (cause) {
132
+ return Effect.fail(new AgentExecutorError({ operation, message, cause }))
89
133
  }
134
+ })
135
+ }
90
136
 
91
- return []
92
- }
137
+ function isAgentExecutorOwner(
138
+ owner: PlanNodeSpec['owner'],
139
+ ): owner is Extract<PlanNodeSpec['owner'], { executorType: 'agent' }> {
140
+ return owner.executorType === 'agent'
141
+ }
142
+
143
+ function buildWriteIntentTool(params: {
144
+ nodeSpec: PlanNodeSpec
145
+ workspace: ReturnType<ReturnType<typeof makeNodeWorkspaceService>['initialize']>
146
+ writeIntentValidatorService: ReturnType<typeof makeWriteIntentValidatorService>
147
+ stageWrite: (intent: WriteIntent) => Effect.Effect<void, BadRequestError>
148
+ }) {
149
+ return tool({
150
+ description:
151
+ 'Write a validated artifact or structured output field. Call this for each deliverable. If validation fails, correct your payload and try again.',
152
+ inputSchema: WriteIntentSchema,
153
+ execute: (intent: WriteIntent) =>
154
+ runPromise(
155
+ Effect.gen(function* () {
156
+ const correctionCount = params.workspace.sys.correctionCounts.get(intent.targetPath) ?? 0
157
+
158
+ const validation = params.writeIntentValidatorService.validate({
159
+ intent,
160
+ nodeSpec: params.nodeSpec,
161
+ schemaRegistry: params.workspace.ctx.schemaRegistry,
162
+ existingDeliverables: params.workspace.deliverables,
163
+ })
164
+
165
+ if (validation.issues.length > 0) {
166
+ if (correctionCount >= MAX_SELF_CORRECTION_RETRIES) {
167
+ return yield* new BadRequestError({
168
+ message: `Write validation failed for "${intent.targetPath}" after ${MAX_SELF_CORRECTION_RETRIES} retries: ${validation.issues.map((i) => i.message).join(', ')}`,
169
+ })
170
+ }
171
+ params.workspace.sys.correctionCounts.set(intent.targetPath, correctionCount + 1)
172
+ params.workspace.sys.writeLog.push({
173
+ targetPath: intent.targetPath,
174
+ action: intent.action,
175
+ timestamp: nowDate(),
176
+ result: 'rejected',
177
+ })
178
+ return {
179
+ status: 'validation_failed',
180
+ error: { targetPath: intent.targetPath, action: intent.action, issues: validation.issues },
181
+ hint: `Correct and re-emit. Attempt ${correctionCount + 1}/${MAX_SELF_CORRECTION_RETRIES}.`,
182
+ }
183
+ }
184
+
185
+ yield* params.stageWrite(intent)
186
+ return { status: 'accepted', message: `Write to "${intent.targetPath}" validated and staged.` }
187
+ }),
188
+ ),
189
+ })
190
+ }
93
191
 
94
- async executeNode(params: {
192
+ function createExecuteNode(deps: AgentExecutorDeps) {
193
+ return function executeNode(params: {
95
194
  nodeSpec: PlanNodeSpec
96
195
  resolvedInput: Record<string, unknown>
97
196
  inputArtifacts: PlanArtifactSubmission[]
98
197
  context: OwnershipDispatchContext
99
198
  executionMode?: ExecutionMode
100
199
  schemaRegistry?: PlanSchemaRegistry
101
- }): Promise<PlanNodeResult> {
102
- if (params.nodeSpec.owner.executorType !== 'agent') {
103
- throw new Error(`AgentExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
104
- }
200
+ }) {
201
+ return Effect.gen(function* () {
202
+ const owner = params.nodeSpec.owner
203
+ if (!isAgentExecutorOwner(owner)) {
204
+ return yield* new BadRequestError({
205
+ message: `AgentExecutor cannot execute owner type "${owner.executorType}".`,
206
+ })
207
+ }
105
208
 
106
- const agentId = params.nodeSpec.owner.ref
107
- if (!agentRoster.includes(agentId)) {
108
- throw new Error(`Agent executor "${agentId}" is not registered.`)
109
- }
209
+ const agentId = owner.ref
210
+ if (!getAgentRoster().includes(agentId)) {
211
+ return yield* new BadRequestError({ message: `Agent executor "${agentId}" is not registered.` })
212
+ }
110
213
 
111
- const thread = await databaseService.findOne(
112
- TABLES.THREAD,
113
- { id: ensureRecordId(params.context.threadId, TABLES.THREAD) },
114
- ThreadSchema,
115
- )
116
- if (!thread) {
117
- throw new Error(`Thread ${params.context.threadId} not found for dispatched execution.`)
118
- }
214
+ const thread = yield* tryAgentExecutorPromise('load-thread', 'Failed to load dispatched thread.', () =>
215
+ deps.db.findOne(TABLES.THREAD, { id: ensureRecordId(params.context.threadId, TABLES.THREAD) }, ThreadSchema),
216
+ )
217
+ if (!thread) {
218
+ return yield* new NotFoundError({
219
+ resource: TABLES.THREAD,
220
+ message: `Thread ${params.context.threadId} not found for dispatched execution.`,
221
+ })
222
+ }
119
223
 
120
- const organizationRef = ensureRecordId(params.context.organizationId, TABLES.ORGANIZATION)
121
- const threadRef = ensureRecordId(params.context.threadId, TABLES.THREAD)
122
- const userRefSource = params.context.userId ?? thread.userId
123
- if (!userRefSource) {
124
- throw new Error(`Thread ${params.context.threadId} is missing a user context for dispatched execution.`)
125
- }
126
- const userRef = ensureRecordId(userRefSource, TABLES.USER)
127
- const userName = params.context.userName ?? 'User'
128
- const getLinearInstallationByOrgId = getPluginService([
129
- 'linear',
130
- 'services',
131
- 'linearService',
132
- 'getInstallationByOrgId',
133
- ])
134
- const getGithubInstallationForOrganization = getPluginService([
135
- 'github',
136
- 'services',
137
- 'githubService',
138
- 'getInstallationForOrganization',
139
- ])
140
- const [linearInstallation, githubInstallation, indexedRepoContext] = await Promise.all([
141
- getLinearInstallationByOrgId ? getLinearInstallationByOrgId(organizationRef) : Promise.resolve(null),
142
- getGithubInstallationForOrganization
143
- ? getGithubInstallationForOrganization(params.context.organizationId)
144
- : Promise.resolve(null),
145
- buildIndexedRepositoriesContext(params.context.organizationId),
146
- ])
147
-
148
- const mode = params.executionMode ?? 'linear'
149
-
150
- const dispatchMode = thread.type === 'group' ? 'fixedThreadMode' : 'direct'
151
- const dispatchInstructionSections = [
152
- buildOwnershipDispatchContextSection({
153
- node: params.nodeSpec,
154
- resolvedInput: params.resolvedInput,
155
- inputArtifacts: params.inputArtifacts,
156
- upstreamHandoffs: params.context.upstreamHandoffs,
157
- }),
158
- ]
159
- const turnHooks = getTurnHooks()
160
- const agentResolution = asRecord(
161
- await turnHooks.resolveAgent?.({
162
- agentId,
224
+ const organizationRef = ensureRecordId(params.context.organizationId, TABLES.ORGANIZATION)
225
+ const threadRef = ensureRecordId(params.context.threadId, TABLES.THREAD)
226
+ const userRefSource = params.context.userId ?? thread.userId
227
+ if (!userRefSource) {
228
+ return yield* new BadRequestError({
229
+ message: `Thread ${params.context.threadId} is missing a user context for dispatched execution.`,
230
+ })
231
+ }
232
+ const userRef = ensureRecordId(userRefSource, TABLES.USER)
233
+ const userName = params.context.userName ?? 'User'
234
+ const [linearInstallation, githubInstallation, indexedRepoContext] = yield* Effect.all([
235
+ tryAgentExecutorPromise(
236
+ 'get-linear-installation',
237
+ `Failed to load Linear installation for org ${params.context.organizationId}.`,
238
+ () => getLinearInstallationByOrgId(organizationRef),
239
+ ),
240
+ tryAgentExecutorPromise(
241
+ 'get-github-installation',
242
+ `Failed to load GitHub installation for org ${params.context.organizationId}.`,
243
+ () => getGithubInstallationForOrganization(params.context.organizationId),
244
+ ),
245
+ tryAgentExecutorPromise(
246
+ 'build-indexed-repositories-context',
247
+ `Failed to build indexed repository context for org ${params.context.organizationId}.`,
248
+ () => buildIndexedRepositoriesContext(params.context.organizationId),
249
+ ),
250
+ ])
251
+
252
+ const mode = params.executionMode ?? 'linear'
253
+ const dispatchMode = thread.type === 'group' ? 'fixedThreadMode' : 'direct'
254
+ const dispatchInstructionSections = [
255
+ buildOwnershipDispatchContextSection({
256
+ node: params.nodeSpec,
257
+ resolvedInput: params.resolvedInput,
258
+ inputArtifacts: params.inputArtifacts,
259
+ upstreamHandoffs: params.context.upstreamHandoffs,
260
+ }),
261
+ ]
262
+
263
+ const turnHooks = getTurnHooks()
264
+ const agentResolutionValue = yield* Effect.tryPromise(() =>
265
+ Promise.resolve(
266
+ turnHooks.resolveAgent?.({
267
+ agentId,
268
+ mode: dispatchMode,
269
+ thread,
270
+ threadRef,
271
+ orgRef: organizationRef,
272
+ userRef,
273
+ userName,
274
+ onboardingActive: false,
275
+ linearInstalled: Boolean(linearInstallation),
276
+ githubInstalled: Boolean(githubInstallation),
277
+ additionalInstructionSections: dispatchInstructionSections,
278
+ context: null,
279
+ }),
280
+ ),
281
+ )
282
+ const agentResolution = isRecord(agentResolutionValue) ? agentResolutionValue : null
283
+ const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
284
+ const runtimeConfig = getAgentRuntimeConfig({
285
+ agentId: resolvedAgentId,
286
+ threadType: thread.type,
163
287
  mode: dispatchMode,
164
- thread,
165
- threadRef,
166
- orgRef: organizationRef,
167
- userRef,
168
- userName,
169
288
  onboardingActive: false,
170
289
  linearInstalled: Boolean(linearInstallation),
171
- githubInstalled: Boolean(githubInstallation),
172
- additionalInstructionSections: dispatchInstructionSections,
173
- context: null,
174
- }),
175
- )
176
- const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
177
- const runtimeConfig = getAgentRuntimeConfig({
178
- agentId: resolvedAgentId,
179
- threadType: thread.type,
180
- mode: dispatchMode,
181
- onboardingActive: false,
182
- linearInstalled: Boolean(linearInstallation),
183
- additionalInstructionSections: mergeInstructionSections(
184
- dispatchInstructionSections,
185
- readInstructionSections(agentResolution?.additionalInstructionSections),
186
- ),
187
- responseGuardSection: buildOwnershipDispatchResponseGuard({ node: params.nodeSpec, executionMode: mode }),
188
- }) as Record<string, unknown>
189
-
190
- const rawTools = (await buildAgentTools({
191
- agentId: resolvedAgentId,
192
- orgId: organizationRef,
193
- userId: userRef,
194
- userName,
195
- threadId: threadRef,
196
- orgIdString: params.context.organizationId,
197
- threadType: thread.type,
198
- mode: dispatchMode,
199
- linearInstalled: Boolean(linearInstallation),
200
- onboardingActive: false,
201
- githubInstalled: Boolean(githubInstallation),
202
- provideRepoTool: indexedRepoContext.provideRepoTool,
203
- defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[resolvedAgentId],
204
- memoryBlock: '',
205
- onAppendMemoryBlock: () => undefined,
206
- availableUploads: [],
207
- includeExecutionPlanTools: false,
208
- })) as ToolSet
209
- const tools = applyToolPolicy(rawTools, params.nodeSpec)
210
-
211
- const agentFactory = createAgent[resolvedAgentId] as ((...args: unknown[]) => unknown) | undefined
212
- if (!agentFactory) {
213
- throw new Error(`Agent factory "${resolvedAgentId}" is not registered.`)
214
- }
290
+ additionalInstructionSections: mergeInstructionSections(
291
+ dispatchInstructionSections,
292
+ readInstructionSections(agentResolution?.additionalInstructionSections),
293
+ ),
294
+ responseGuardSection: buildOwnershipDispatchResponseGuard({ node: params.nodeSpec, executionMode: mode }),
295
+ })
296
+
297
+ const agentFactoryConfig = getResolvedAgentFactoryConfig()
298
+ const rawTools = yield* tryAgentExecutorPromise(
299
+ 'build-agent-tools',
300
+ `Failed to build agent tools for "${resolvedAgentId}".`,
301
+ () =>
302
+ Promise.resolve(
303
+ agentFactoryConfig.buildAgentTools({
304
+ agentId: resolvedAgentId,
305
+ orgId: organizationRef,
306
+ userId: userRef,
307
+ userName,
308
+ threadId: threadRef,
309
+ orgIdString: params.context.organizationId,
310
+ threadType: thread.type,
311
+ mode: dispatchMode,
312
+ linearInstalled: Boolean(linearInstallation),
313
+ onboardingActive: false,
314
+ githubInstalled: Boolean(githubInstallation),
315
+ provideRepoTool: indexedRepoContext.provideRepoTool,
316
+ defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[resolvedAgentId],
317
+ memoryBlock: '',
318
+ onAppendMemoryBlock: () => undefined,
319
+ availableUploads: [],
320
+ includeExecutionPlanTools: false,
321
+ }),
322
+ ),
323
+ )
324
+ const tools = applyToolPolicy(rawTools, params.nodeSpec)
325
+
326
+ const agentFactory = agentFactoryConfig.createAgent[resolvedAgentId]
327
+ if (!agentFactory) {
328
+ return yield* new BadRequestError({ message: `Agent factory "${resolvedAgentId}" is not registered.` })
329
+ }
215
330
 
216
- const maxSteps = typeof runtimeConfig.maxSteps === 'number' ? runtimeConfig.maxSteps : 8
331
+ const maxSteps = typeof runtimeConfig.maxSteps === 'number' ? runtimeConfig.maxSteps : 8
217
332
 
218
- if (mode === 'linear') {
333
+ if (mode === 'linear') {
334
+ const agent = agentFactory({
335
+ mode: dispatchMode,
336
+ tools,
337
+ extraInstructions:
338
+ typeof runtimeConfig.extraInstructions === 'string' ? runtimeConfig.extraInstructions : undefined,
339
+ maxRetries: 1,
340
+ stopWhen: [stepCountIs(maxSteps)],
341
+ })
342
+
343
+ const result = yield* tryAgentExecutorPromise(
344
+ 'generate-agent-dispatch',
345
+ `Agent "${resolvedAgentId}" failed to generate a dispatch result.`,
346
+ () => agent.generate({ prompt: buildDispatchPrompt(params.nodeSpec) }),
347
+ )
348
+ const outputCandidate = PlanNodeResultSubmissionSchema.safeParse(result.output)
349
+ if (outputCandidate.success) {
350
+ return outputCandidate.data
351
+ }
352
+
353
+ return yield* toValidationError(
354
+ outputCandidate.error,
355
+ `Agent executor "${agentId}" returned an invalid node result`,
356
+ )
357
+ }
358
+
359
+ const workspace = deps.nodeWorkspaceService.initialize({
360
+ nodeSpec: params.nodeSpec,
361
+ resolvedInput: params.resolvedInput,
362
+ inputArtifacts: params.inputArtifacts,
363
+ schemaRegistry: params.schemaRegistry ?? {},
364
+ })
365
+
366
+ const graphTools = {
367
+ ...tools,
368
+ writeIntent: buildWriteIntentTool({
369
+ nodeSpec: params.nodeSpec,
370
+ workspace,
371
+ writeIntentValidatorService: deps.writeIntentValidatorService,
372
+ stageWrite: (intent) => deps.nodeWorkspaceService.stageWrite(workspace, intent, 'validated'),
373
+ }),
374
+ }
219
375
  const agent = agentFactory({
220
376
  mode: dispatchMode,
221
- tools,
377
+ tools: graphTools,
222
378
  extraInstructions:
223
379
  typeof runtimeConfig.extraInstructions === 'string' ? runtimeConfig.extraInstructions : undefined,
224
380
  maxRetries: 1,
225
381
  stopWhen: [stepCountIs(maxSteps)],
226
- }) as ToolLoopAgent<never, ToolSet>
227
-
228
- const result = await agent.generate({ prompt: buildDispatchPrompt(params.nodeSpec) })
229
- const outputCandidate = PlanNodeResultSubmissionSchema.safeParse(result.output)
230
- if (outputCandidate.success) {
231
- return outputCandidate.data
232
- }
233
-
234
- throw new Error(`Agent executor "${agentId}" returned an invalid node result.`)
235
- }
382
+ })
236
383
 
237
- // --- Graph-lite / Graph-full path ---
384
+ yield* Effect.tryPromise(() => agent.generate({ prompt: buildWriteIntentDispatchPrompt(params.nodeSpec) }))
238
385
 
239
- const workspace = nodeWorkspaceService.initialize({
240
- nodeSpec: params.nodeSpec,
241
- resolvedInput: params.resolvedInput,
242
- inputArtifacts: params.inputArtifacts,
243
- schemaRegistry: params.schemaRegistry ?? {},
244
- })
245
-
246
- const writeIntentTool = tool({
247
- description:
248
- 'Write a validated artifact or structured output field. Call this for each deliverable. If validation fails, correct your payload and try again.',
249
- inputSchema: WriteIntentSchema,
250
- execute: async (intent: WriteIntent) => {
251
- const correctionCount = workspace.sys.correctionCounts.get(intent.targetPath) ?? 0
252
-
253
- const validation = writeIntentValidatorService.validate({
254
- intent,
255
- nodeSpec: params.nodeSpec,
256
- schemaRegistry: workspace.ctx.schemaRegistry,
257
- existingDeliverables: workspace.deliverables,
258
- })
386
+ const finalResult = yield* deps.nodeWorkspaceService.finalize(workspace)
259
387
 
260
- if (validation.issues.length > 0) {
261
- if (correctionCount >= MAX_SELF_CORRECTION_RETRIES) {
262
- throw new Error(
263
- `Write validation failed for "${intent.targetPath}" after ${MAX_SELF_CORRECTION_RETRIES} retries: ${validation.issues.map((i) => i.message).join(', ')}`,
264
- )
265
- }
266
- workspace.sys.correctionCounts.set(intent.targetPath, correctionCount + 1)
267
- workspace.sys.writeLog.push({
268
- targetPath: intent.targetPath,
269
- action: intent.action,
270
- timestamp: new Date(),
271
- result: 'rejected',
272
- })
273
- return {
274
- status: 'validation_failed',
275
- error: { targetPath: intent.targetPath, action: intent.action, issues: validation.issues },
276
- hint: `Correct and re-emit. Attempt ${correctionCount + 1}/${MAX_SELF_CORRECTION_RETRIES}.`,
277
- }
388
+ if (!finalResult.isComplete) {
389
+ const result: PlanNodeResult = {
390
+ structuredOutput: finalResult.structuredOutput,
391
+ artifacts: finalResult.artifacts,
392
+ notes: 'Execution incomplete: missing required deliverables or validation failures.',
278
393
  }
394
+ deps.nodeWorkspaceService.cleanup(workspace)
395
+ return result
396
+ }
279
397
 
280
- nodeWorkspaceService.stageWrite(workspace, intent, 'validated')
281
- return { status: 'accepted', message: `Write to "${intent.targetPath}" validated and staged.` }
282
- },
283
- })
284
-
285
- const graphTools = { ...tools, writeIntent: writeIntentTool }
286
-
287
- const agent = agentFactory({
288
- mode: dispatchMode,
289
- tools: graphTools,
290
- extraInstructions:
291
- typeof runtimeConfig.extraInstructions === 'string' ? runtimeConfig.extraInstructions : undefined,
292
- maxRetries: 1,
293
- stopWhen: [stepCountIs(maxSteps)],
294
- }) as ToolLoopAgent<never, ToolSet>
295
-
296
- await agent.generate({ prompt: buildWriteIntentDispatchPrompt(params.nodeSpec) })
297
-
298
- const finalResult = nodeWorkspaceService.finalize(workspace)
299
-
300
- if (!finalResult.isComplete) {
301
398
  const result: PlanNodeResult = {
302
399
  structuredOutput: finalResult.structuredOutput,
303
400
  artifacts: finalResult.artifacts,
304
- notes: 'Execution incomplete: missing required deliverables or validation failures.',
401
+ notes: finalResult.notes,
305
402
  }
306
- nodeWorkspaceService.cleanup(workspace)
403
+
404
+ deps.nodeWorkspaceService.cleanup(workspace)
307
405
  return result
308
- }
406
+ })
407
+ }
408
+ }
309
409
 
310
- const result: PlanNodeResult = {
311
- structuredOutput: finalResult.structuredOutput,
312
- artifacts: finalResult.artifacts,
313
- notes: finalResult.notes,
314
- }
410
+ interface AgentExecutorDeps {
411
+ db: SurrealDBService
412
+ nodeWorkspaceService: ReturnType<typeof makeNodeWorkspaceService>
413
+ writeIntentValidatorService: ReturnType<typeof makeWriteIntentValidatorService>
414
+ }
315
415
 
316
- nodeWorkspaceService.cleanup(workspace)
317
- return result
318
- }
416
+ export function makeAgentExecutorService(deps: AgentExecutorDeps) {
417
+ return { validateOwner, executeNode: createExecuteNode(deps) }
319
418
  }
320
419
 
321
- export const agentExecutorService = new AgentExecutorService()
420
+ export class AgentExecutorServiceTag extends Context.Service<
421
+ AgentExecutorServiceTag,
422
+ ReturnType<typeof makeAgentExecutorService>
423
+ >()('AgentExecutorService') {}
424
+
425
+ export const AgentExecutorServiceLive = Layer.effect(
426
+ AgentExecutorServiceTag,
427
+ Effect.gen(function* () {
428
+ const db = yield* DatabaseServiceTag
429
+ const nodeWorkspaceService = yield* NodeWorkspaceServiceTag
430
+ const writeIntentValidatorService = yield* WriteIntentValidatorServiceTag
431
+ return makeAgentExecutorService({ db, nodeWorkspaceService, writeIntentValidatorService })
432
+ }),
433
+ )