@lota-sdk/core 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -0,0 +1,60 @@
1
+ import { PlanArtifactSchema } from '@lota-sdk/shared'
2
+ import type { PlanArtifactRecord, PlanArtifactSubmission } from '@lota-sdk/shared'
3
+ import { Context, Effect, Layer } from 'effect'
4
+ import { RecordId } from 'surrealdb'
5
+
6
+ import type { RecordIdInput } from '../../db/record-id'
7
+ import { ensureRecordId } from '../../db/record-id'
8
+ import type { DatabaseTransaction } from '../../db/service'
9
+ import { TABLES } from '../../db/tables'
10
+
11
+ export function makePlanArtifactService() {
12
+ return {
13
+ persistArtifacts(params: {
14
+ tx: DatabaseTransaction
15
+ runId: RecordIdInput
16
+ attemptId: RecordIdInput
17
+ nodeId: string
18
+ artifacts: PlanArtifactSubmission[]
19
+ }) {
20
+ return Effect.gen(function* () {
21
+ const records: PlanArtifactRecord[] = []
22
+
23
+ // Sequential: SurrealDB transactions require ordered operations
24
+ for (const artifact of params.artifacts) {
25
+ const artifactId = new RecordId(TABLES.PLAN_ARTIFACT, Bun.randomUUIDv7())
26
+ const created = yield* params.tx
27
+ .create(artifactId)
28
+ .content({
29
+ runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
30
+ attemptId: ensureRecordId(params.attemptId, TABLES.PLAN_NODE_ATTEMPT),
31
+ nodeId: params.nodeId,
32
+ name: artifact.name,
33
+ kind: artifact.kind,
34
+ pointer: artifact.publishedArtifactId
35
+ ? `venturos://artifact/${artifact.publishedArtifactId}`
36
+ : `artifact://${params.nodeId}/${artifact.name}`,
37
+ ...(artifact.description ? { description: artifact.description } : {}),
38
+ ...(artifact.content ? { content: artifact.content } : {}),
39
+ ...(artifact.payload ? { payload: artifact.payload } : {}),
40
+ ...(artifact.publishedArtifactId
41
+ ? { publishedArtifactId: ensureRecordId(artifact.publishedArtifactId, TABLES.ARTIFACT) }
42
+ : {}),
43
+ })
44
+ .output('after')
45
+
46
+ records.push(PlanArtifactSchema.parse(created))
47
+ }
48
+
49
+ return records
50
+ })
51
+ },
52
+ }
53
+ }
54
+
55
+ export class PlanArtifactServiceTag extends Context.Service<
56
+ PlanArtifactServiceTag,
57
+ ReturnType<typeof makePlanArtifactService>
58
+ >()('@lota-sdk/core/PlanArtifactService') {}
59
+
60
+ export const PlanArtifactServiceLive = Layer.sync(PlanArtifactServiceTag, () => makePlanArtifactService())
@@ -0,0 +1,76 @@
1
+ import type { PlanDraft } from '@lota-sdk/shared'
2
+ import { Context, Layer } from 'effect'
3
+
4
+ import { isExecutableConditionExpression } from './plan-helpers'
5
+
6
+ function buildImplicitLinearEdges(draft: PlanDraft) {
7
+ if (draft.edges.length > 0 || draft.nodes.length <= 1) {
8
+ return draft.edges
9
+ }
10
+
11
+ return draft.nodes
12
+ .slice(0, -1)
13
+ .map((node, index) => ({
14
+ id: `edge_${node.id}_to_${draft.nodes[index + 1]?.id ?? index + 1}`,
15
+ source: node.id,
16
+ target: draft.nodes[index + 1].id,
17
+ map: {},
18
+ }))
19
+ }
20
+
21
+ function structureDesign(draft: PlanDraft): PlanDraft {
22
+ return {
23
+ ...draft,
24
+ executionMode: draft.executionMode ?? 'linear',
25
+ edges: buildImplicitLinearEdges(draft),
26
+ entryNodeIds: draft.entryNodeIds && draft.entryNodeIds.length > 0 ? draft.entryNodeIds : [draft.nodes[0].id],
27
+ }
28
+ }
29
+
30
+ function semanticCompletion(draft: PlanDraft): PlanDraft {
31
+ return {
32
+ ...draft,
33
+ nodes: draft.nodes.map((node) => ({
34
+ ...node,
35
+ deliverables: [...node.deliverables],
36
+ successCriteria: [...node.successCriteria],
37
+ completionChecks: [...node.completionChecks],
38
+ failurePolicy: [...node.failurePolicy],
39
+ retryPolicy: { ...node.retryPolicy, retryOn: [...node.retryPolicy.retryOn] },
40
+ toolPolicy: { allow: [...node.toolPolicy.allow], deny: [...node.toolPolicy.deny] },
41
+ contextPolicy: {
42
+ retrievalScopes: [...node.contextPolicy.retrievalScopes],
43
+ attachmentPolicy: node.contextPolicy.attachmentPolicy,
44
+ webPolicy: node.contextPolicy.webPolicy,
45
+ },
46
+ })),
47
+ edges: draft.edges.map((edge) => {
48
+ const normalizedWhen = edge.when?.trim()
49
+ const { when: _when, ...edgeWithoutWhen } = edge
50
+ return {
51
+ ...edgeWithoutWhen,
52
+ ...(normalizedWhen && isExecutableConditionExpression(normalizedWhen) ? { when: normalizedWhen } : {}),
53
+ map: { ...edge.map },
54
+ }
55
+ }),
56
+ schemas: structuredClone(draft.schemas),
57
+ entryNodeIds: [...(draft.entryNodeIds ?? [])],
58
+ }
59
+ }
60
+
61
+ export function makePlanBuilderService() {
62
+ return {
63
+ structureDesign,
64
+ semanticCompletion,
65
+ prepareDraft(draft: PlanDraft): PlanDraft {
66
+ return semanticCompletion(structureDesign(draft))
67
+ },
68
+ }
69
+ }
70
+
71
+ export class PlanBuilderServiceTag extends Context.Service<
72
+ PlanBuilderServiceTag,
73
+ ReturnType<typeof makePlanBuilderService>
74
+ >()('@lota-sdk/core/PlanBuilderService') {}
75
+
76
+ export const PlanBuilderServiceLive = Layer.sync(PlanBuilderServiceTag, () => makePlanBuilderService())
@@ -0,0 +1,103 @@
1
+ import { PlanCheckpointSchema } from '@lota-sdk/shared'
2
+ import type { PlanRunStatus } from '@lota-sdk/shared'
3
+ import { Context, Effect, Layer } from 'effect'
4
+ import { RecordId } from 'surrealdb'
5
+
6
+ import { serverLogger } from '../../config/logger'
7
+ import type { RecordIdInput } from '../../db/record-id'
8
+ import { ensureRecordId, recordIdToString } from '../../db/record-id'
9
+ import type { SurrealDBService, DatabaseTransaction } from '../../db/service'
10
+ import { TABLES } from '../../db/tables'
11
+ import { DatabaseServiceTag } from '../../effect/services'
12
+ import type { makePlanWorkspaceService } from './plan-workspace.service'
13
+ import { PlanWorkspaceServiceTag } from './plan-workspace.service'
14
+
15
+ interface PlanCheckpointDeps {
16
+ db: SurrealDBService
17
+ planWorkspaceService: ReturnType<typeof makePlanWorkspaceService>
18
+ }
19
+
20
+ export function makePlanCheckpointService(deps: PlanCheckpointDeps) {
21
+ const { db, planWorkspaceService } = deps
22
+
23
+ return {
24
+ createCheckpoint: (params: {
25
+ tx: DatabaseTransaction
26
+ runId: RecordIdInput
27
+ sequence: number
28
+ runStatus: PlanRunStatus
29
+ readyNodeIds: string[]
30
+ activeNodeIds: string[]
31
+ artifactIds: RecordIdInput[]
32
+ lastCompletedNodeIds: string[]
33
+ snapshot: Record<string, unknown>
34
+ includeWorkspace?: boolean
35
+ }) =>
36
+ Effect.gen(function* () {
37
+ const snapshotData = { ...params.snapshot }
38
+
39
+ if (params.includeWorkspace) {
40
+ const runIdStr = recordIdToString(params.runId, TABLES.PLAN_RUN)
41
+ const workspaceSnapshotEffect = planWorkspaceService.currentRead({ runId: runIdStr }).pipe(
42
+ Effect.map((all: Record<string, { writeSequence: number }>) => {
43
+ const filtered: Record<string, unknown> = {}
44
+ for (const [key, entry] of Object.entries(all)) {
45
+ if (entry.writeSequence <= params.sequence) {
46
+ filtered[key] = entry
47
+ }
48
+ }
49
+ return filtered
50
+ }),
51
+ Effect.orElseSucceed(() => {
52
+ serverLogger.warn`Workspace snapshot failed for run ${runIdStr}`
53
+ return {} as Record<string, unknown>
54
+ }),
55
+ )
56
+ const workspaceSnapshot: Record<string, unknown> = yield* workspaceSnapshotEffect
57
+ if (Object.keys(workspaceSnapshot).length > 0) {
58
+ snapshotData.workspaceSnapshot = workspaceSnapshot
59
+ }
60
+ }
61
+
62
+ const checkpointId = new RecordId(TABLES.PLAN_CHECKPOINT, Bun.randomUUIDv7())
63
+ const created = yield* params.tx
64
+ .create(checkpointId)
65
+ .content({
66
+ runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
67
+ sequence: params.sequence,
68
+ runStatus: params.runStatus,
69
+ readyNodeIds: [...params.readyNodeIds],
70
+ activeNodeIds: [...params.activeNodeIds],
71
+ artifactIds: params.artifactIds.map((artifactId) => ensureRecordId(artifactId, TABLES.PLAN_ARTIFACT)),
72
+ lastCompletedNodeIds: [...params.lastCompletedNodeIds],
73
+ snapshot: snapshotData,
74
+ })
75
+ .output('after')
76
+
77
+ return PlanCheckpointSchema.parse(created)
78
+ }),
79
+
80
+ loadLatestForRun: (runId: RecordIdInput) =>
81
+ db
82
+ .findMany(TABLES.PLAN_CHECKPOINT, { runId: ensureRecordId(runId, TABLES.PLAN_RUN) }, PlanCheckpointSchema, {
83
+ orderBy: 'sequence',
84
+ orderDir: 'DESC',
85
+ limit: 1,
86
+ })
87
+ .pipe(Effect.map((checkpoints) => checkpoints.at(0) ?? null)),
88
+ }
89
+ }
90
+
91
+ export class PlanCheckpointServiceTag extends Context.Service<
92
+ PlanCheckpointServiceTag,
93
+ ReturnType<typeof makePlanCheckpointService>
94
+ >()('@lota-sdk/core/PlanCheckpointService') {}
95
+
96
+ export const PlanCheckpointServiceLive = Layer.effect(
97
+ PlanCheckpointServiceTag,
98
+ Effect.gen(function* () {
99
+ const db = yield* DatabaseServiceTag
100
+ const planWorkspaceService = yield* PlanWorkspaceServiceTag
101
+ return makePlanCheckpointService({ db, planWorkspaceService })
102
+ }),
103
+ )
@@ -1,6 +1,9 @@
1
1
  import type { PlanDraft, PlanNodeSpec, PlanNodeSpecRecord } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
2
3
 
3
- import { planValidatorService } from './plan-validator.service'
4
+ import { ValidationError } from '../../effect/errors'
5
+ import type { makePlanValidatorService } from './plan-validator.service'
6
+ import { PlanValidatorServiceTag } from './plan-validator.service'
4
7
 
5
8
  export interface CompiledPlanNode {
6
9
  node: PlanNodeSpec
@@ -14,12 +17,14 @@ interface CompiledPlanDraft {
14
17
  nodes: CompiledPlanNode[]
15
18
  }
16
19
 
17
- class PlanCompilerService {
18
- compile(draft: PlanDraft): CompiledPlanDraft {
20
+ export function makePlanCompilerService(planValidatorService: ReturnType<typeof makePlanValidatorService>) {
21
+ function compile(draft: PlanDraft): Effect.Effect<CompiledPlanDraft, ValidationError> {
19
22
  const validation = planValidatorService.validateDraft(draft)
20
23
  if (validation.blocking.length > 0) {
21
- throw new Error(
22
- `Plan draft failed validation: ${validation.blocking.map((issue) => `${issue.code}: ${issue.message}`).join(' | ')}`,
24
+ return Effect.fail(
25
+ new ValidationError({
26
+ message: `Plan draft failed validation: ${validation.blocking.map((issue) => `${issue.code}: ${issue.message}`).join(' | ')}`,
27
+ }),
23
28
  )
24
29
  }
25
30
 
@@ -36,7 +41,7 @@ class PlanCompilerService {
36
41
  downstreamByNodeId.get(edge.source)?.push(edge.target)
37
42
  }
38
43
 
39
- return {
44
+ return Effect.succeed({
40
45
  draft,
41
46
  nodes: draft.nodes.map((node, index) => ({
42
47
  node,
@@ -44,10 +49,10 @@ class PlanCompilerService {
44
49
  upstreamNodeIds: [...(upstreamByNodeId.get(node.id) ?? [])],
45
50
  downstreamNodeIds: [...(downstreamByNodeId.get(node.id) ?? [])],
46
51
  })),
47
- }
52
+ })
48
53
  }
49
54
 
50
- toNodeSpecRecords(
55
+ function toNodeSpecRecords(
51
56
  compiled: CompiledPlanDraft,
52
57
  ): Array<Omit<PlanNodeSpecRecord, 'id' | 'planSpecId' | 'createdAt' | 'updatedAt'>> {
53
58
  return compiled.nodes.map(({ node, position, upstreamNodeIds, downstreamNodeIds }) => ({
@@ -83,6 +88,18 @@ class PlanCompilerService {
83
88
  downstreamNodeIds,
84
89
  }))
85
90
  }
91
+
92
+ return { compile, toNodeSpecRecords }
86
93
  }
87
94
 
88
- export const planCompilerService = new PlanCompilerService()
95
+ export class PlanCompilerServiceTag extends Context.Service<
96
+ PlanCompilerServiceTag,
97
+ ReturnType<typeof makePlanCompilerService>
98
+ >()('@lota-sdk/core/PlanCompilerService') {}
99
+
100
+ export const PlanCompilerServiceLive = Layer.effect(
101
+ PlanCompilerServiceTag,
102
+ Effect.map(PlanValidatorServiceTag.asEffect(), (planValidatorService) =>
103
+ makePlanCompilerService(planValidatorService),
104
+ ),
105
+ )
@@ -0,0 +1,169 @@
1
+ import type { PlanNodeOwner, PlanNodeType } from '@lota-sdk/shared'
2
+ import { PlanEventSchema } from '@lota-sdk/shared'
3
+ import { Schema, Effect, Metric } from 'effect'
4
+
5
+ import { aiLogger } from '../../config/logger'
6
+ import { ensureRecordId } from '../../db/record-id'
7
+ import type { SurrealDBService } from '../../db/service'
8
+ import { TABLES } from '../../db/tables'
9
+ import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
10
+ import { runPromise } from '../../effect/runtime'
11
+ import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
12
+ import type { makeFeedbackLoopService } from '../feedback-loop.service'
13
+ import type { makeInstitutionalMemoryService } from '../institutional-memory.service'
14
+ import type { makeQualityMetricsService } from '../quality-metrics.service'
15
+ import type { makePlanEventDeliveryService } from './plan-event-delivery.service'
16
+ import type { makePlanRunService } from './plan-run.service'
17
+ import type { PlanValidationIssueInput } from './plan-validator.service'
18
+
19
+ const planNodeExecutionDuration = Metric.histogram('plan_node_execution_duration_ms', {
20
+ boundaries: Metric.boundariesFromIterable([100, 500, 1000, 5000, 10_000, 30_000, 60_000]),
21
+ })
22
+
23
+ interface PlanCompletionSideEffectsDeps {
24
+ databaseService: SurrealDBService
25
+ feedbackLoopService: ReturnType<typeof makeFeedbackLoopService>
26
+ institutionalMemoryService: ReturnType<typeof makeInstitutionalMemoryService>
27
+ planEventDeliveryService: ReturnType<typeof makePlanEventDeliveryService>
28
+ planRunService: ReturnType<typeof makePlanRunService>
29
+ qualityMetricsService: ReturnType<typeof makeQualityMetricsService>
30
+ }
31
+
32
+ class PlanCompletionSideEffectsError extends Schema.TaggedErrorClass<PlanCompletionSideEffectsError>()(
33
+ 'PlanCompletionSideEffectsError',
34
+ { message: Schema.String, cause: Schema.Defect },
35
+ ) {}
36
+
37
+ const effectTryPlanCompletionPromise = makeEffectTryPromiseWithMessage(
38
+ (message, cause) => new PlanCompletionSideEffectsError({ message, cause }),
39
+ )
40
+
41
+ function tryPlanCompletionPromise<A>(
42
+ message: string,
43
+ evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
44
+ ): Effect.Effect<A, PlanCompletionSideEffectsError> {
45
+ return effectTryPlanCompletionPromise(evaluate, message)
46
+ }
47
+
48
+ export function makePlanCompletionSideEffects({
49
+ databaseService,
50
+ feedbackLoopService,
51
+ institutionalMemoryService,
52
+ planEventDeliveryService,
53
+ planRunService,
54
+ qualityMetricsService,
55
+ }: PlanCompletionSideEffectsDeps) {
56
+ function runPlanNodeCompletionSideEffects(params: {
57
+ runId: string
58
+ organizationId: string
59
+ nodeId: string
60
+ nodeLabel: string
61
+ nodeOwnerRef: string
62
+ nodeOwnerType: PlanNodeOwner['executorType']
63
+ nodeType: PlanNodeType
64
+ nodeStartedAt?: string | Date | null
65
+ nodeAttemptCount: number
66
+ artifactCount: number
67
+ validationIssues: PlanValidationIssueInput[]
68
+ }): Promise<void> {
69
+ return runPromise(
70
+ Effect.gen(function* () {
71
+ const executionTimeMs = params.nodeStartedAt
72
+ ? nowEpochMillis() - unsafeDateFrom(params.nodeStartedAt).getTime()
73
+ : 0
74
+ if (executionTimeMs > 0) {
75
+ yield* Metric.update(
76
+ Metric.withAttributes(planNodeExecutionDuration, { nodeType: params.nodeType }),
77
+ executionTimeMs,
78
+ )
79
+ }
80
+ yield* qualityMetricsService.recordNodeMetrics({
81
+ organizationId: params.organizationId,
82
+ runId: params.runId,
83
+ nodeId: params.nodeId,
84
+ metrics: {
85
+ executionTimeMs: Math.max(0, executionTimeMs),
86
+ attemptCount: params.nodeAttemptCount,
87
+ artifactCount: params.artifactCount,
88
+ validationIssueCount: params.validationIssues.length,
89
+ ownerRef: params.nodeOwnerRef,
90
+ ownerType: params.nodeOwnerType,
91
+ nodeType: params.nodeType,
92
+ },
93
+ })
94
+ }),
95
+ )
96
+ }
97
+
98
+ function runPlanCompletionSideEffects(params: { runId: string; organizationId: string }): Promise<void> {
99
+ return runPromise(
100
+ Effect.gen(function* () {
101
+ yield* qualityMetricsService.recordCycleMetrics({ organizationId: params.organizationId, runId: params.runId })
102
+
103
+ const recommendations = yield* feedbackLoopService
104
+ .analyzeOutcomes({ runId: params.runId, organizationId: params.organizationId })
105
+ .pipe(
106
+ Effect.mapError(
107
+ (cause) =>
108
+ new PlanCompletionSideEffectsError({ message: 'Failed to analyze plan feedback outcomes.', cause }),
109
+ ),
110
+ )
111
+ if (recommendations.length > 0) {
112
+ const run = yield* planRunService.getRunById(params.runId)
113
+ const specRecord = yield* planRunService.getPlanSpecById(run.planSpecId)
114
+ const event = yield* tryPlanCompletionPromise('Failed to create feedback analyzed plan event.', () =>
115
+ databaseService.create(
116
+ TABLES.PLAN_EVENT,
117
+ {
118
+ planSpecId: ensureRecordId(specRecord.id, TABLES.PLAN_SPEC),
119
+ runId: ensureRecordId(run.id, TABLES.PLAN_RUN),
120
+ eventType: 'feedback-analyzed',
121
+ message: `Feedback analysis produced ${recommendations.length} recommendation(s).`,
122
+ detail: { recommendations },
123
+ emittedBy: 'system',
124
+ },
125
+ PlanEventSchema,
126
+ ),
127
+ )
128
+ yield* planEventDeliveryService
129
+ .dispatchEventEffect(event)
130
+ .pipe(
131
+ Effect.mapError(
132
+ (error) =>
133
+ new PlanCompletionSideEffectsError({
134
+ message: 'Failed to dispatch feedback analyzed plan event.',
135
+ cause: error,
136
+ }),
137
+ ),
138
+ )
139
+ }
140
+
141
+ yield* institutionalMemoryService
142
+ .extractPatterns({ organizationId: params.organizationId, runId: params.runId })
143
+ .pipe(
144
+ Effect.mapError(
145
+ (cause) =>
146
+ new PlanCompletionSideEffectsError({
147
+ message: 'Failed to extract institutional memory patterns.',
148
+ cause,
149
+ }),
150
+ ),
151
+ )
152
+ }),
153
+ )
154
+ }
155
+
156
+ function runPlanCompletionSideEffectsSafely(params: { runId: string; organizationId: string }): Promise<void> {
157
+ return runPromise(
158
+ Effect.catch(
159
+ tryPlanCompletionPromise('Plan completion side effects failed.', () => runPlanCompletionSideEffects(params)),
160
+ (error: unknown) =>
161
+ Effect.sync(() => {
162
+ aiLogger.warn`Plan completion side effects failed for run ${params.runId}: ${error instanceof Error ? error.message : String(error)}`
163
+ }),
164
+ ),
165
+ )
166
+ }
167
+
168
+ return { runPlanNodeCompletionSideEffects, runPlanCompletionSideEffectsSafely }
169
+ }
@@ -0,0 +1,181 @@
1
+ import type { PlanArtifactRecord, PlanDependency } from '@lota-sdk/shared'
2
+ import { Context, Schema, Effect, Layer } from 'effect'
3
+
4
+ import { serverLogger } from '../../config/logger'
5
+ import { recordIdToString } from '../../db/record-id'
6
+ import { TABLES } from '../../db/tables'
7
+ import { nowEpochMillis } from '../../utils/date-time'
8
+ import type { makePlanRunService } from './plan-run.service'
9
+ import { PlanRunServiceTag } from './plan-run.service'
10
+ import type { PlanValidationIssueInput } from './plan-validator.service'
11
+
12
+ export interface DependencyResolutionResult {
13
+ resolved: Map<string, PlanArtifactRecord>
14
+ unresolved: PlanDependency[]
15
+ notifications: Array<{ dependency: PlanDependency; reason: string }>
16
+ }
17
+
18
+ class PlanCoordinationError extends Schema.TaggedErrorClass<PlanCoordinationError>()('PlanCoordinationError', {
19
+ message: Schema.String,
20
+ cause: Schema.optional(Schema.Defect),
21
+ }) {}
22
+
23
+ function isStale(artifact: Pick<PlanArtifactRecord, 'createdAt'>, maxStalenessMs: number): boolean {
24
+ if (!maxStalenessMs) return false
25
+ return nowEpochMillis() - artifact.createdAt.getTime() > maxStalenessMs
26
+ }
27
+
28
+ function validateNoCycles(
29
+ specs: Array<{ id: string; title?: string; dependencies?: PlanDependency[] }>,
30
+ ): PlanValidationIssueInput[] {
31
+ const adj = new Map<string, Set<string>>()
32
+ const inDegree = new Map<string, number>()
33
+ const labels = new Map(specs.map((spec) => [spec.id, spec.title ?? spec.id]))
34
+
35
+ for (const spec of specs) {
36
+ if (!adj.has(spec.id)) adj.set(spec.id, new Set())
37
+ if (!inDegree.has(spec.id)) inDegree.set(spec.id, 0)
38
+
39
+ for (const dep of spec.dependencies ?? []) {
40
+ if (!adj.has(dep.sourcePlanSpecId)) adj.set(dep.sourcePlanSpecId, new Set())
41
+ if (!inDegree.has(dep.sourcePlanSpecId)) inDegree.set(dep.sourcePlanSpecId, 0)
42
+
43
+ adj.get(dep.sourcePlanSpecId)?.add(spec.id)
44
+ inDegree.set(spec.id, (inDegree.get(spec.id) ?? 0) + 1)
45
+ }
46
+ }
47
+
48
+ const queue = [...inDegree.entries()].filter(([, d]) => d === 0).map(([t]) => t)
49
+ const visited = new Set<string>()
50
+
51
+ while (queue.length > 0) {
52
+ const node = queue.shift()
53
+ if (!node) break
54
+ visited.add(node)
55
+ for (const dep of adj.get(node) ?? []) {
56
+ const d = (inDegree.get(dep) ?? 0) - 1
57
+ inDegree.set(dep, d)
58
+ if (d === 0) queue.push(dep)
59
+ }
60
+ }
61
+
62
+ const unvisited = specs.filter((s) => !visited.has(s.id))
63
+ if (unvisited.length === 0) return []
64
+
65
+ return [
66
+ {
67
+ severity: 'blocking',
68
+ code: 'circular_dependency',
69
+ message: `Circular plan dependencies detected involving: ${unvisited.map((s) => labels.get(s.id) ?? s.id).join(', ')}`,
70
+ },
71
+ ]
72
+ }
73
+
74
+ export function makePlanCoordinationService(planRunService: ReturnType<typeof makePlanRunService>) {
75
+ const resolveDependencies = (params: { dependencies: PlanDependency[]; threadId: string }) => {
76
+ const resolved = new Map<string, PlanArtifactRecord>()
77
+ const unresolved: PlanDependency[] = []
78
+ const notifications: DependencyResolutionResult['notifications'] = []
79
+
80
+ return Effect.gen(function* () {
81
+ const specs = yield* planRunService
82
+ .listPlanSpecsByThread(params.threadId)
83
+ .pipe(
84
+ Effect.mapError(
85
+ (cause) => new PlanCoordinationError({ message: 'Failed to list plan specs by thread.', cause }),
86
+ ),
87
+ )
88
+
89
+ for (const dep of params.dependencies) {
90
+ const depKey = `${dep.sourcePlanSpecId}:${dep.sourceNodeId}:${dep.artifactName}`
91
+ const sourceSpec = specs.find((spec) => recordIdToString(spec.id, TABLES.PLAN_SPEC) === dep.sourcePlanSpecId)
92
+ if (!sourceSpec) {
93
+ const reason = `Source plan "${dep.sourcePlanSpecId}" not found in thread.`
94
+ if (dep.triggerMode === 'block') {
95
+ unresolved.push(dep)
96
+ } else if (dep.triggerMode === 'notify') {
97
+ notifications.push({ dependency: dep, reason })
98
+ serverLogger.warn`Dependency unmet (notify): ${reason}`
99
+ }
100
+ continue
101
+ }
102
+
103
+ const runs = yield* planRunService
104
+ .listRunsBySpec(sourceSpec.id)
105
+ .pipe(
106
+ Effect.mapError(
107
+ (cause) =>
108
+ new PlanCoordinationError({ message: 'Failed to list plan runs for dependency resolution.', cause }),
109
+ ),
110
+ )
111
+ const activeRun = runs.find((run) => run.status === 'completed' || run.status === 'running')
112
+ if (!activeRun) {
113
+ const reason = `No active run found for plan "${sourceSpec.title}".`
114
+ if (dep.triggerMode === 'block') {
115
+ unresolved.push(dep)
116
+ } else if (dep.triggerMode === 'notify') {
117
+ notifications.push({ dependency: dep, reason })
118
+ serverLogger.warn`Dependency unmet (notify): ${reason}`
119
+ }
120
+ continue
121
+ }
122
+
123
+ const artifacts = yield* planRunService
124
+ .listArtifacts(activeRun.id)
125
+ .pipe(
126
+ Effect.mapError(
127
+ (cause) =>
128
+ new PlanCoordinationError({
129
+ message: 'Failed to list plan artifacts for dependency resolution.',
130
+ cause,
131
+ }),
132
+ ),
133
+ )
134
+ const artifact = artifacts.find(
135
+ (candidate) => candidate.nodeId === dep.sourceNodeId && candidate.name === dep.artifactName,
136
+ )
137
+ if (!artifact) {
138
+ const reason = `Artifact "${dep.artifactName}" not found on node "${dep.sourceNodeId}" in plan "${sourceSpec.title}".`
139
+ if (dep.triggerMode === 'block') {
140
+ unresolved.push(dep)
141
+ } else if (dep.triggerMode === 'notify') {
142
+ notifications.push({ dependency: dep, reason })
143
+ serverLogger.warn`Dependency unmet (notify): ${reason}`
144
+ }
145
+ continue
146
+ }
147
+
148
+ if (dep.maxStalenessMs && isStale(artifact, dep.maxStalenessMs)) {
149
+ const reason = `Artifact "${dep.artifactName}" from plan "${sourceSpec.title}" is stale.`
150
+ if (dep.triggerMode === 'block') {
151
+ unresolved.push(dep)
152
+ continue
153
+ }
154
+ if (dep.triggerMode === 'notify') {
155
+ notifications.push({ dependency: dep, reason })
156
+ serverLogger.warn`Dependency stale (notify): ${reason}`
157
+ }
158
+ }
159
+
160
+ resolved.set(depKey, artifact)
161
+ }
162
+
163
+ return { resolved, unresolved, notifications }
164
+ })
165
+ }
166
+
167
+ return { resolveDependencies, isStale, validateNoCycles }
168
+ }
169
+
170
+ export class PlanCoordinationServiceTag extends Context.Service<
171
+ PlanCoordinationServiceTag,
172
+ ReturnType<typeof makePlanCoordinationService>
173
+ >()('@lota-sdk/core/PlanCoordinationService') {}
174
+
175
+ export const PlanCoordinationServiceLive = Layer.effect(
176
+ PlanCoordinationServiceTag,
177
+ Effect.gen(function* () {
178
+ const planRunService = yield* PlanRunServiceTag
179
+ return makePlanCoordinationService(planRunService)
180
+ }),
181
+ )