@lota-sdk/core 0.1.23 → 0.1.25

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 (78) hide show
  1. package/package.json +2 -2
  2. package/src/ai/definitions.ts +5 -59
  3. package/src/ai-gateway/ai-gateway.ts +36 -28
  4. package/src/ai-gateway/cache-headers.ts +9 -0
  5. package/src/config/model-constants.ts +6 -2
  6. package/src/create-runtime.ts +5 -17
  7. package/src/db/memory-types.ts +13 -8
  8. package/src/db/memory.ts +74 -53
  9. package/src/queues/autonomous-job.queue.ts +1 -8
  10. package/src/queues/context-compaction.queue.ts +2 -2
  11. package/src/queues/index.ts +2 -6
  12. package/src/queues/organization-learning.queue.ts +78 -0
  13. package/src/queues/plan-agent-heartbeat.queue.ts +10 -16
  14. package/src/queues/title-generation.queue.ts +62 -0
  15. package/src/runtime/agent-prompt-context.ts +0 -18
  16. package/src/runtime/agent-runtime-policy.ts +9 -2
  17. package/src/runtime/context-compaction-constants.ts +4 -2
  18. package/src/runtime/context-compaction.ts +135 -118
  19. package/src/runtime/execution-plan.ts +2 -1
  20. package/src/runtime/memory-pipeline.ts +70 -1
  21. package/src/runtime/memory-prompts-fact.ts +16 -0
  22. package/src/runtime/plugin-resolution.ts +3 -2
  23. package/src/runtime/plugin-types.ts +1 -42
  24. package/src/runtime/post-turn-side-effects.ts +212 -0
  25. package/src/runtime/runtime-config.ts +0 -13
  26. package/src/runtime/runtime-extensions.ts +10 -16
  27. package/src/runtime/runtime-worker-registry.ts +8 -19
  28. package/src/runtime/social-chat-agent-runner.ts +119 -0
  29. package/src/runtime/social-chat-history.ts +110 -0
  30. package/src/runtime/social-chat-prompts.ts +58 -0
  31. package/src/runtime/social-chat.ts +104 -340
  32. package/src/runtime/specialist-runner.ts +18 -0
  33. package/src/runtime/workstream-chat-helpers.ts +19 -0
  34. package/src/runtime/workstream-plan-turn.ts +195 -0
  35. package/src/runtime/workstream-state.ts +11 -8
  36. package/src/runtime/workstream-turn-context.ts +183 -0
  37. package/src/services/agent-activity.service.ts +350 -0
  38. package/src/services/autonomous-job.service.ts +1 -8
  39. package/src/services/execution-plan.service.ts +205 -334
  40. package/src/services/index.ts +2 -4
  41. package/src/services/memory.service.ts +54 -44
  42. package/src/services/ownership-dispatcher.service.ts +2 -19
  43. package/src/services/plan-completion-side-effects.ts +80 -0
  44. package/src/services/plan-event-delivery.service.ts +1 -1
  45. package/src/services/plan-executor.service.ts +42 -190
  46. package/src/services/plan-node-spec.ts +60 -0
  47. package/src/services/plan-run-data.ts +88 -0
  48. package/src/services/plan-validator.service.ts +10 -8
  49. package/src/services/workstream-constants.ts +2 -0
  50. package/src/services/workstream-title.service.ts +1 -1
  51. package/src/services/workstream-turn-preparation.service.ts +208 -715
  52. package/src/services/workstream.service.ts +162 -192
  53. package/src/services/workstream.types.ts +12 -44
  54. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -0
  55. package/src/tools/execution-plan.tool.ts +11 -6
  56. package/src/tools/index.ts +1 -0
  57. package/src/tools/project-with-plan.tool.ts +87 -0
  58. package/src/tools/remember-memory.tool.ts +7 -10
  59. package/src/tools/research-topic.tool.ts +1 -1
  60. package/src/tools/team-think.tool.ts +1 -1
  61. package/src/tools/user-questions.tool.ts +1 -1
  62. package/src/utils/autonomous-job-ids.ts +7 -0
  63. package/src/workers/organization-learning.worker.ts +31 -0
  64. package/src/workers/regular-chat-memory-digest.runner.ts +9 -3
  65. package/src/workers/skill-extraction.runner.ts +2 -2
  66. package/src/queues/recent-activity-title-refinement.queue.ts +0 -30
  67. package/src/queues/regular-chat-memory-digest.config.ts +0 -12
  68. package/src/queues/regular-chat-memory-digest.queue.ts +0 -34
  69. package/src/queues/skill-extraction.config.ts +0 -9
  70. package/src/queues/skill-extraction.queue.ts +0 -27
  71. package/src/queues/workstream-title-generation.queue.ts +0 -33
  72. package/src/services/context-enrichment.service.ts +0 -33
  73. package/src/services/coordination-registry.service.ts +0 -117
  74. package/src/services/domain-agent-executor.service.ts +0 -71
  75. package/src/services/memory-assessment.service.ts +0 -44
  76. package/src/services/playbook-registry.service.ts +0 -67
  77. package/src/workers/regular-chat-memory-digest.worker.ts +0 -22
  78. package/src/workers/skill-extraction.worker.ts +0 -22
@@ -13,10 +13,12 @@ import type {
13
13
  SerializableExecutionPlan,
14
14
  } from '@lota-sdk/shared'
15
15
  import {
16
+ HUMAN_NODE_TYPES as HUMAN_NODE_TYPE_VALUES,
16
17
  PlanEventSchema,
17
18
  PlanNodeAttemptSchema,
18
19
  PlanNodeRunSchema,
19
20
  PlanRunSchema,
21
+ STRUCTURAL_NODE_TYPES as STRUCTURAL_NODE_TYPE_VALUES,
20
22
  PlanValidationIssueSchema,
21
23
  } from '@lota-sdk/shared'
22
24
  import { RecordId } from 'surrealdb'
@@ -29,23 +31,24 @@ import type { DatabaseTransaction } from '../db/service'
29
31
  import { TABLES } from '../db/tables'
30
32
  import { toDatabaseDateTime } from '../utils/date-time'
31
33
  import { isRecord } from '../utils/string'
32
- import { feedbackLoopService } from './feedback-loop.service'
33
- import { institutionalMemoryService } from './institutional-memory.service'
34
34
  import { planApprovalService } from './plan-approval.service'
35
35
  import { planArtifactService } from './plan-artifact.service'
36
36
  import { planCheckpointService } from './plan-checkpoint.service'
37
+ import { runPlanCompletionSideEffectsSafely, runPlanNodeCompletionSideEffects } from './plan-completion-side-effects'
37
38
  import { planCoordinationService } from './plan-coordination.service'
38
39
  import { planEventDeliveryService } from './plan-event-delivery.service'
39
40
  import { isExecutableConditionExpression, readPathValue } from './plan-helpers'
41
+ import { toPlanNodeValidationSpec } from './plan-node-spec'
42
+ import { buildExecutionPlanToolResult, toRunData } from './plan-run-data'
43
+ import type { PlanRunUpdate } from './plan-run-data'
40
44
  import { planRunService } from './plan-run.service'
41
45
  import { planSchedulerService } from './plan-scheduler.service'
42
46
  import type { PlanValidationIssueInput } from './plan-validator.service'
43
47
  import { planValidatorService } from './plan-validator.service'
44
- import { qualityMetricsService } from './quality-metrics.service'
45
48
 
46
49
  const SUCCESSFUL_TERMINAL_NODE_STATUSES = new Set(['completed', 'partial', 'skipped', 'scheduled', 'monitoring'])
47
- const HUMAN_NODE_TYPES = new Set(['human-input', 'human-approval', 'human-review-edit', 'human-decision'])
48
- const STRUCTURAL_NODE_TYPES = new Set(['switch', 'join', 'deliberation-fork'])
50
+ const HUMAN_NODE_TYPE_SET = new Set<string>(HUMAN_NODE_TYPE_VALUES)
51
+ const STRUCTURAL_NODE_TYPE_SET = new Set<string>(STRUCTURAL_NODE_TYPE_VALUES)
49
52
 
50
53
  function setPathValue(target: Record<string, unknown>, path: string, value: unknown) {
51
54
  const segments = path
@@ -142,23 +145,11 @@ function isSuccessfulTerminalStatus(status: string): boolean {
142
145
  }
143
146
 
144
147
  function isHumanNodeType(type: string): boolean {
145
- return HUMAN_NODE_TYPES.has(type)
148
+ return HUMAN_NODE_TYPE_SET.has(type)
146
149
  }
147
150
 
148
151
  function isStructuralNodeType(type: string): boolean {
149
- return STRUCTURAL_NODE_TYPES.has(type)
150
- }
151
-
152
- type PlanRunUpdate = Omit<
153
- Partial<PlanRunRecord>,
154
- 'currentNodeId' | 'waitingNodeId' | 'replacedRunId' | 'lastCheckpointId' | 'startedAt' | 'completedAt'
155
- > & {
156
- currentNodeId?: string | null
157
- waitingNodeId?: string | null
158
- replacedRunId?: RecordIdInput | null
159
- lastCheckpointId?: RecordIdInput | null
160
- startedAt?: string | Date | null
161
- completedAt?: string | Date | null
152
+ return STRUCTURAL_NODE_TYPE_SET.has(type)
162
153
  }
163
154
 
164
155
  type PlanNodeRunUpdate = Omit<
@@ -188,60 +179,6 @@ type PlanNodeRunUpdate = Omit<
188
179
  completedAt?: string | Date | null
189
180
  }
190
181
 
191
- function toRunData(run: PlanRunRecord, patch: PlanRunUpdate) {
192
- return {
193
- planSpecId: ensureRecordId(run.planSpecId, TABLES.PLAN_SPEC),
194
- organizationId: ensureRecordId(run.organizationId, TABLES.ORGANIZATION),
195
- workstreamId: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM),
196
- leadAgentId: patch.leadAgentId ?? run.leadAgentId,
197
- status: patch.status ?? run.status,
198
- ...(patch.currentNodeId === null
199
- ? {}
200
- : patch.currentNodeId !== undefined
201
- ? { currentNodeId: patch.currentNodeId }
202
- : run.currentNodeId
203
- ? { currentNodeId: run.currentNodeId }
204
- : {}),
205
- ...(patch.waitingNodeId === null
206
- ? {}
207
- : patch.waitingNodeId !== undefined
208
- ? { waitingNodeId: patch.waitingNodeId }
209
- : run.waitingNodeId
210
- ? { waitingNodeId: run.waitingNodeId }
211
- : {}),
212
- readyNodeIds: patch.readyNodeIds ? [...patch.readyNodeIds] : [...run.readyNodeIds],
213
- failureCount: patch.failureCount ?? run.failureCount,
214
- ...(patch.replacedRunId === null
215
- ? {}
216
- : patch.replacedRunId
217
- ? { replacedRunId: ensureRecordId(patch.replacedRunId, TABLES.PLAN_RUN) }
218
- : run.replacedRunId
219
- ? { replacedRunId: ensureRecordId(run.replacedRunId, TABLES.PLAN_RUN) }
220
- : {}),
221
- ...(patch.lastCheckpointId === null
222
- ? {}
223
- : patch.lastCheckpointId
224
- ? { lastCheckpointId: ensureRecordId(patch.lastCheckpointId, TABLES.PLAN_CHECKPOINT) }
225
- : run.lastCheckpointId
226
- ? { lastCheckpointId: ensureRecordId(run.lastCheckpointId, TABLES.PLAN_CHECKPOINT) }
227
- : {}),
228
- ...(patch.startedAt === null
229
- ? {}
230
- : patch.startedAt !== undefined
231
- ? { startedAt: toDatabaseDateTime(patch.startedAt) }
232
- : run.startedAt
233
- ? { startedAt: toDatabaseDateTime(run.startedAt) }
234
- : {}),
235
- ...(patch.completedAt === null
236
- ? {}
237
- : patch.completedAt !== undefined
238
- ? { completedAt: toDatabaseDateTime(patch.completedAt) }
239
- : run.completedAt
240
- ? { completedAt: toDatabaseDateTime(run.completedAt) }
241
- : {}),
242
- }
243
- }
244
-
245
182
  function toNodeRunData(nodeRun: PlanNodeRunRecord, patch: PlanNodeRunUpdate) {
246
183
  return {
247
184
  runId: ensureRecordId(nodeRun.runId, TABLES.PLAN_RUN),
@@ -330,22 +267,6 @@ function toNodeRunData(nodeRun: PlanNodeRunRecord, patch: PlanNodeRunUpdate) {
330
267
  }
331
268
  }
332
269
 
333
- function buildToolResult(params: {
334
- action: ExecutionPlanToolResultData['action']
335
- plan: SerializableExecutionPlan | null
336
- message: string
337
- changedNodeId?: string
338
- }): ExecutionPlanToolResultData {
339
- return {
340
- action: params.action,
341
- message: params.message,
342
- ...(params.changedNodeId ? { changedNodeId: params.changedNodeId } : {}),
343
- ...(params.plan ? { plan: params.plan } : {}),
344
- hasPlan: params.plan !== null,
345
- status: params.plan?.status,
346
- }
347
- }
348
-
349
270
  function deriveApprovalStatus(response: Record<string, unknown>): 'approved' | 'rejected' | 'changes-requested' {
350
271
  const approved = response.approved === true
351
272
  const requiredEdits = Array.isArray(response.requiredEdits)
@@ -396,30 +317,7 @@ class PlanExecutorService {
396
317
  const latestCheckpoint = await planRunService.getLatestCheckpoint(run.id)
397
318
  const validation = planValidatorService.validateNodeResult({
398
319
  draft: { schemas: spec.schemaRegistry },
399
- node: {
400
- id: nodeSpec.nodeId,
401
- type: nodeSpec.type,
402
- label: nodeSpec.label,
403
- owner: nodeSpec.owner,
404
- objective: nodeSpec.objective,
405
- instructions: nodeSpec.instructions,
406
- inputSchemaRef: nodeSpec.inputSchemaRef,
407
- outputSchemaRef: nodeSpec.outputSchemaRef,
408
- deliverables: [...nodeSpec.deliverables],
409
- successCriteria: [...nodeSpec.successCriteria],
410
- completionChecks: [...nodeSpec.completionChecks],
411
- retryPolicy: { ...nodeSpec.retryPolicy, retryOn: [...nodeSpec.retryPolicy.retryOn] },
412
- failurePolicy: [...nodeSpec.failurePolicy],
413
- timeoutMs: nodeSpec.timeoutMs,
414
- toolPolicy: { allow: [...nodeSpec.toolPolicy.allow], deny: [...nodeSpec.toolPolicy.deny] },
415
- contextPolicy: {
416
- retrievalScopes: [...nodeSpec.contextPolicy.retrievalScopes],
417
- attachmentPolicy: nodeSpec.contextPolicy.attachmentPolicy,
418
- webPolicy: nodeSpec.contextPolicy.webPolicy,
419
- },
420
- executionVisibility: nodeSpec.executionVisibility,
421
- ...(nodeSpec.escalation ? { escalation: nodeSpec.escalation } : {}),
422
- },
320
+ node: toPlanNodeValidationSpec(nodeSpec),
423
321
  result: params.result,
424
322
  })
425
323
  const emittedEvents: PlanEventRecord[] = []
@@ -557,6 +455,7 @@ class PlanExecutorService {
557
455
  const checkpoint = await this.saveCheckpoint({
558
456
  tx,
559
457
  run: synced.run,
458
+ spec,
560
459
  nodeRuns: synced.nodeRuns,
561
460
  artifacts: synced.artifacts,
562
461
  sequence: (latestCheckpoint?.sequence ?? 0) + 1,
@@ -627,6 +526,7 @@ class PlanExecutorService {
627
526
  const checkpoint = await this.saveCheckpoint({
628
527
  tx,
629
528
  run: failedRun,
529
+ spec,
630
530
  nodeRuns: withUpdatedNodeRuns.map((candidate) =>
631
531
  candidate.nodeId === nextNodeRun.nodeId ? nextNodeRun : candidate,
632
532
  ),
@@ -681,6 +581,7 @@ class PlanExecutorService {
681
581
  const checkpoint = await this.saveCheckpoint({
682
582
  tx,
683
583
  run: blockedRun,
584
+ spec,
684
585
  nodeRuns: withUpdatedNodeRuns.map((candidate) =>
685
586
  candidate.nodeId === nextNodeRun.nodeId ? nextNodeRun : candidate,
686
587
  ),
@@ -736,6 +637,7 @@ class PlanExecutorService {
736
637
  const checkpoint = await this.saveCheckpoint({
737
638
  tx,
738
639
  run: failedRun,
640
+ spec,
739
641
  nodeRuns: withUpdatedNodeRuns.map((candidate) =>
740
642
  candidate.nodeId === nextNodeRun.nodeId ? nextNodeRun : candidate,
741
643
  ),
@@ -801,6 +703,7 @@ class PlanExecutorService {
801
703
  const checkpoint = await this.saveCheckpoint({
802
704
  tx,
803
705
  run: synced.run,
706
+ spec,
804
707
  nodeRuns: synced.nodeRuns,
805
708
  artifacts: synced.artifacts,
806
709
  sequence: (latestCheckpoint?.sequence ?? 0) + 1,
@@ -811,61 +714,27 @@ class PlanExecutorService {
811
714
  await this.attachCheckpoint(tx, synced.run, checkpoint)
812
715
  })
813
716
 
814
- // Record node-level quality metrics (fire-and-forget)
815
717
  const orgId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
816
718
  const runIdStr = recordIdToString(run.id, TABLES.PLAN_RUN)
817
- const nodeStartedAt = nodeRun.startedAt
818
- const executionTimeMs = nodeStartedAt ? Date.now() - new Date(nodeStartedAt).getTime() : 0
819
- qualityMetricsService
820
- .recordNodeMetrics({
821
- organizationId: orgId,
822
- runId: runIdStr,
823
- nodeId: params.nodeId,
824
- metrics: {
825
- executionTimeMs: Math.max(0, executionTimeMs),
826
- attemptCount: nodeRun.attemptCount + 1,
827
- artifactCount: params.result.artifacts.length,
828
- validationIssueCount: validation.blocking.length + validation.warnings.length,
829
- ownerRef: nodeSpec.owner.ref,
830
- ownerType: nodeSpec.owner.executorType,
831
- nodeType: nodeSpec.type,
832
- },
833
- })
834
- .catch((error) => {
835
- aiLogger.warn`Failed to record node quality metrics for run ${runIdStr} node ${params.nodeId}: ${error instanceof Error ? error.message : String(error)}`
836
- })
719
+ void runPlanNodeCompletionSideEffects({
720
+ runId: runIdStr,
721
+ organizationId: orgId,
722
+ nodeId: params.nodeId,
723
+ nodeLabel: nodeSpec.label,
724
+ nodeOwnerRef: nodeSpec.owner.ref,
725
+ nodeOwnerType: nodeSpec.owner.executorType,
726
+ nodeType: nodeSpec.type,
727
+ nodeStartedAt: nodeRun.startedAt,
728
+ nodeAttemptCount: nodeRun.attemptCount + 1,
729
+ artifactCount: params.result.artifacts.length,
730
+ validationIssues: [...validation.blocking, ...validation.warnings],
731
+ }).catch((error) => {
732
+ aiLogger.warn`Failed to record node completion metrics for run ${runIdStr} node ${params.nodeId}: ${error instanceof Error ? error.message : String(error)}`
733
+ })
837
734
 
838
- // If the run just completed, record cycle metrics, persist feedback recommendations, and extract patterns
839
735
  const updatedRun = await planRunService.getRunById(run.id)
840
736
  if (updatedRun.status === 'completed') {
841
- qualityMetricsService.recordCycleMetrics({ organizationId: orgId, runId: runIdStr }).catch((error) => {
842
- aiLogger.warn`Failed to record cycle quality metrics for run ${runIdStr}: ${error instanceof Error ? error.message : String(error)}`
843
- })
844
- feedbackLoopService
845
- .analyzeOutcomes({ runId: runIdStr, organizationId: orgId })
846
- .then(async (recommendations) => {
847
- if (recommendations.length === 0) return
848
- const specRecord = await planRunService.getPlanSpecById(updatedRun.planSpecId)
849
- const event = await databaseService.create(
850
- TABLES.PLAN_EVENT,
851
- {
852
- planSpecId: ensureRecordId(specRecord.id, TABLES.PLAN_SPEC),
853
- runId: ensureRecordId(updatedRun.id, TABLES.PLAN_RUN),
854
- eventType: 'feedback-analyzed',
855
- message: `Feedback analysis produced ${recommendations.length} recommendation(s).`,
856
- detail: { recommendations },
857
- emittedBy: 'system',
858
- },
859
- PlanEventSchema,
860
- )
861
- await planEventDeliveryService.dispatchEvent(event)
862
- })
863
- .catch((error) => {
864
- aiLogger.warn`Failed to analyze feedback outcomes for run ${runIdStr}: ${error instanceof Error ? error.message : String(error)}`
865
- })
866
- institutionalMemoryService.extractPatterns({ organizationId: orgId, runId: runIdStr }).catch((error) => {
867
- aiLogger.warn`Failed to extract institutional memory patterns for run ${runIdStr}: ${error instanceof Error ? error.message : String(error)}`
868
- })
737
+ void runPlanCompletionSideEffectsSafely({ runId: runIdStr, organizationId: orgId })
869
738
  }
870
739
 
871
740
  await planEventDeliveryService.dispatchEvents(emittedEvents)
@@ -878,7 +747,7 @@ class PlanExecutorService {
878
747
  includeValidationIssues: true,
879
748
  })
880
749
 
881
- return buildToolResult({
750
+ return buildExecutionPlanToolResult({
882
751
  action: 'node-result-submitted',
883
752
  plan: snapshot,
884
753
  message: `Submitted result for node "${nodeSpec.label}".`,
@@ -917,30 +786,7 @@ class PlanExecutorService {
917
786
  const latestCheckpoint = await planRunService.getLatestCheckpoint(run.id)
918
787
  const validation = planValidatorService.validateNodeResult({
919
788
  draft: { schemas: spec.schemaRegistry },
920
- node: {
921
- id: nodeSpec.nodeId,
922
- type: nodeSpec.type,
923
- label: nodeSpec.label,
924
- owner: nodeSpec.owner,
925
- objective: nodeSpec.objective,
926
- instructions: nodeSpec.instructions,
927
- inputSchemaRef: nodeSpec.inputSchemaRef,
928
- outputSchemaRef: nodeSpec.outputSchemaRef,
929
- deliverables: [...nodeSpec.deliverables],
930
- successCriteria: [...nodeSpec.successCriteria],
931
- completionChecks: [...nodeSpec.completionChecks],
932
- retryPolicy: { ...nodeSpec.retryPolicy, retryOn: [...nodeSpec.retryPolicy.retryOn] },
933
- failurePolicy: [...nodeSpec.failurePolicy],
934
- timeoutMs: nodeSpec.timeoutMs,
935
- toolPolicy: { allow: [...nodeSpec.toolPolicy.allow], deny: [...nodeSpec.toolPolicy.deny] },
936
- contextPolicy: {
937
- retrievalScopes: [...nodeSpec.contextPolicy.retrievalScopes],
938
- attachmentPolicy: nodeSpec.contextPolicy.attachmentPolicy,
939
- webPolicy: nodeSpec.contextPolicy.webPolicy,
940
- },
941
- executionVisibility: nodeSpec.executionVisibility,
942
- ...(nodeSpec.escalation ? { escalation: nodeSpec.escalation } : {}),
943
- },
789
+ node: toPlanNodeValidationSpec(nodeSpec),
944
790
  result: {
945
791
  structuredOutput: params.response,
946
792
  artifacts: [],
@@ -1072,6 +918,7 @@ class PlanExecutorService {
1072
918
  const checkpoint = await this.saveCheckpoint({
1073
919
  tx,
1074
920
  run: blockedRun,
921
+ spec,
1075
922
  nodeRuns,
1076
923
  artifacts: existingArtifacts,
1077
924
  sequence: (latestCheckpoint?.sequence ?? 0) + 1,
@@ -1112,6 +959,7 @@ class PlanExecutorService {
1112
959
  const checkpoint = await this.saveCheckpoint({
1113
960
  tx,
1114
961
  run: synced.run,
962
+ spec,
1115
963
  nodeRuns: synced.nodeRuns,
1116
964
  artifacts: synced.artifacts,
1117
965
  sequence: (latestCheckpoint?.sequence ?? 0) + 1,
@@ -1210,6 +1058,7 @@ class PlanExecutorService {
1210
1058
  const checkpoint = await this.saveCheckpoint({
1211
1059
  tx,
1212
1060
  run: synced.run,
1061
+ spec,
1213
1062
  nodeRuns: synced.nodeRuns,
1214
1063
  artifacts: synced.artifacts,
1215
1064
  sequence: (latestCheckpoint?.sequence ?? 0) + 1,
@@ -1229,7 +1078,7 @@ class PlanExecutorService {
1229
1078
  includeValidationIssues: true,
1230
1079
  })
1231
1080
 
1232
- return buildToolResult({
1081
+ return buildExecutionPlanToolResult({
1233
1082
  action: 'run-resumed',
1234
1083
  plan: snapshot,
1235
1084
  message: `Resumed execution run "${snapshot.title}".`,
@@ -1325,6 +1174,7 @@ class PlanExecutorService {
1325
1174
  const checkpoint = await this.saveCheckpoint({
1326
1175
  tx,
1327
1176
  run: blockedRun,
1177
+ spec,
1328
1178
  nodeRuns: (await planRunService.listNodeRuns(run.id)).map((candidate) =>
1329
1179
  candidate.nodeId === blockedNodeRun.nodeId ? blockedNodeRun : candidate,
1330
1180
  ),
@@ -1405,6 +1255,7 @@ class PlanExecutorService {
1405
1255
  const checkpoint = await this.saveCheckpoint({
1406
1256
  tx,
1407
1257
  run: synced.run,
1258
+ spec,
1408
1259
  nodeRuns: synced.nodeRuns,
1409
1260
  artifacts: synced.artifacts,
1410
1261
  sequence: (latestCheckpoint?.sequence ?? 0) + 1,
@@ -1996,6 +1847,7 @@ class PlanExecutorService {
1996
1847
  private async saveCheckpoint(params: {
1997
1848
  tx: DatabaseTransaction
1998
1849
  run: PlanRunRecord
1850
+ spec: PlanSpecRecord
1999
1851
  nodeRuns: PlanNodeRunRecord[]
2000
1852
  artifacts: Array<{ id: RecordIdInput; nodeId: string }>
2001
1853
  sequence: number
@@ -2028,7 +1880,7 @@ class PlanExecutorService {
2028
1880
  await this.emitEvent({
2029
1881
  tx: params.tx,
2030
1882
  run: params.run,
2031
- spec: await planRunService.getPlanSpecById(params.run.planSpecId),
1883
+ spec: params.spec,
2032
1884
  eventType: 'checkpoint-saved',
2033
1885
  message: `Saved checkpoint ${checkpoint.sequence}.`,
2034
1886
  detail: { checkpointId: recordIdToString(checkpoint.id, TABLES.PLAN_CHECKPOINT), reason: params.reason },
@@ -0,0 +1,60 @@
1
+ import type { PlanNodeSpec, PlanNodeSpecRecord } from '@lota-sdk/shared'
2
+
3
+ export type PlanNodeValidationSpec = Pick<
4
+ PlanNodeSpec,
5
+ | 'id'
6
+ | 'type'
7
+ | 'label'
8
+ | 'owner'
9
+ | 'objective'
10
+ | 'instructions'
11
+ | 'inputSchemaRef'
12
+ | 'outputSchemaRef'
13
+ | 'deliverables'
14
+ | 'successCriteria'
15
+ | 'completionChecks'
16
+ | 'retryPolicy'
17
+ | 'failurePolicy'
18
+ | 'timeoutMs'
19
+ | 'toolPolicy'
20
+ | 'contextPolicy'
21
+ | 'executionVisibility'
22
+ | 'schedule'
23
+ | 'deadline'
24
+ | 'escalation'
25
+ | 'monitoringConfig'
26
+ | 'delayAfterPredecessorMs'
27
+ | 'deliberationConfig'
28
+ >
29
+
30
+ export function toPlanNodeValidationSpec(nodeSpec: PlanNodeSpecRecord): PlanNodeValidationSpec {
31
+ return {
32
+ id: nodeSpec.nodeId,
33
+ type: nodeSpec.type,
34
+ label: nodeSpec.label,
35
+ owner: nodeSpec.owner,
36
+ objective: nodeSpec.objective,
37
+ instructions: nodeSpec.instructions,
38
+ inputSchemaRef: nodeSpec.inputSchemaRef,
39
+ outputSchemaRef: nodeSpec.outputSchemaRef,
40
+ deliverables: [...nodeSpec.deliverables],
41
+ successCriteria: [...nodeSpec.successCriteria],
42
+ completionChecks: [...nodeSpec.completionChecks],
43
+ retryPolicy: { ...nodeSpec.retryPolicy, retryOn: [...nodeSpec.retryPolicy.retryOn] },
44
+ failurePolicy: [...nodeSpec.failurePolicy],
45
+ timeoutMs: nodeSpec.timeoutMs,
46
+ toolPolicy: { allow: [...nodeSpec.toolPolicy.allow], deny: [...nodeSpec.toolPolicy.deny] },
47
+ contextPolicy: {
48
+ retrievalScopes: [...nodeSpec.contextPolicy.retrievalScopes],
49
+ attachmentPolicy: nodeSpec.contextPolicy.attachmentPolicy,
50
+ webPolicy: nodeSpec.contextPolicy.webPolicy,
51
+ },
52
+ executionVisibility: nodeSpec.executionVisibility,
53
+ ...(nodeSpec.schedule ? { schedule: nodeSpec.schedule } : {}),
54
+ ...(nodeSpec.deadline ? { deadline: nodeSpec.deadline } : {}),
55
+ ...(nodeSpec.escalation ? { escalation: nodeSpec.escalation } : {}),
56
+ ...(nodeSpec.monitoringConfig ? { monitoringConfig: nodeSpec.monitoringConfig } : {}),
57
+ ...(nodeSpec.delayAfterPredecessorMs ? { delayAfterPredecessorMs: nodeSpec.delayAfterPredecessorMs } : {}),
58
+ ...(nodeSpec.deliberationConfig ? { deliberationConfig: nodeSpec.deliberationConfig } : {}),
59
+ }
60
+ }
@@ -0,0 +1,88 @@
1
+ import type { ExecutionPlanToolResultData, PlanRunRecord } from '@lota-sdk/shared'
2
+
3
+ import type { RecordIdInput } from '../db/record-id'
4
+ import { ensureRecordId } from '../db/record-id'
5
+ import { TABLES } from '../db/tables'
6
+ import { toDatabaseDateTime } from '../utils/date-time'
7
+
8
+ export type PlanRunUpdate = Omit<
9
+ Partial<PlanRunRecord>,
10
+ 'currentNodeId' | 'waitingNodeId' | 'replacedRunId' | 'lastCheckpointId' | 'startedAt' | 'completedAt'
11
+ > & {
12
+ currentNodeId?: string | null
13
+ waitingNodeId?: string | null
14
+ replacedRunId?: RecordIdInput | null
15
+ lastCheckpointId?: RecordIdInput | null
16
+ startedAt?: string | Date | null
17
+ completedAt?: string | Date | null
18
+ }
19
+
20
+ export function toRunData(run: PlanRunRecord, patch: PlanRunUpdate) {
21
+ return {
22
+ planSpecId: ensureRecordId(run.planSpecId, TABLES.PLAN_SPEC),
23
+ organizationId: ensureRecordId(run.organizationId, TABLES.ORGANIZATION),
24
+ workstreamId: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM),
25
+ leadAgentId: patch.leadAgentId ?? run.leadAgentId,
26
+ status: patch.status ?? run.status,
27
+ ...(patch.currentNodeId === null
28
+ ? {}
29
+ : patch.currentNodeId !== undefined
30
+ ? { currentNodeId: patch.currentNodeId }
31
+ : run.currentNodeId
32
+ ? { currentNodeId: run.currentNodeId }
33
+ : {}),
34
+ ...(patch.waitingNodeId === null
35
+ ? {}
36
+ : patch.waitingNodeId !== undefined
37
+ ? { waitingNodeId: patch.waitingNodeId }
38
+ : run.waitingNodeId
39
+ ? { waitingNodeId: run.waitingNodeId }
40
+ : {}),
41
+ readyNodeIds: patch.readyNodeIds ? [...patch.readyNodeIds] : [...run.readyNodeIds],
42
+ failureCount: patch.failureCount ?? run.failureCount,
43
+ ...(patch.replacedRunId === null
44
+ ? {}
45
+ : patch.replacedRunId
46
+ ? { replacedRunId: ensureRecordId(patch.replacedRunId, TABLES.PLAN_RUN) }
47
+ : run.replacedRunId
48
+ ? { replacedRunId: ensureRecordId(run.replacedRunId, TABLES.PLAN_RUN) }
49
+ : {}),
50
+ ...(patch.lastCheckpointId === null
51
+ ? {}
52
+ : patch.lastCheckpointId
53
+ ? { lastCheckpointId: ensureRecordId(patch.lastCheckpointId, TABLES.PLAN_CHECKPOINT) }
54
+ : run.lastCheckpointId
55
+ ? { lastCheckpointId: ensureRecordId(run.lastCheckpointId, TABLES.PLAN_CHECKPOINT) }
56
+ : {}),
57
+ ...(patch.startedAt === null
58
+ ? {}
59
+ : patch.startedAt !== undefined
60
+ ? { startedAt: toDatabaseDateTime(patch.startedAt) }
61
+ : run.startedAt
62
+ ? { startedAt: toDatabaseDateTime(run.startedAt) }
63
+ : {}),
64
+ ...(patch.completedAt === null
65
+ ? {}
66
+ : patch.completedAt !== undefined
67
+ ? { completedAt: toDatabaseDateTime(patch.completedAt) }
68
+ : run.completedAt
69
+ ? { completedAt: toDatabaseDateTime(run.completedAt) }
70
+ : {}),
71
+ }
72
+ }
73
+
74
+ export function buildExecutionPlanToolResult(params: {
75
+ action: ExecutionPlanToolResultData['action']
76
+ plan: ExecutionPlanToolResultData['plan'] | null
77
+ message: string
78
+ changedNodeId?: string
79
+ }): ExecutionPlanToolResultData {
80
+ return {
81
+ action: params.action,
82
+ message: params.message,
83
+ ...(params.changedNodeId ? { changedNodeId: params.changedNodeId } : {}),
84
+ ...(params.plan ? { plan: params.plan } : {}),
85
+ hasPlan: params.plan !== null,
86
+ status: params.plan?.status,
87
+ }
88
+ }
@@ -3,17 +3,19 @@ import type {
3
3
  PlanDataSchema,
4
4
  PlanDraft,
5
5
  PlanFailureClass,
6
+ PlanNodeType,
6
7
  PlanNodeResultSubmission,
7
- PlanNodeSpec,
8
8
  PlanValidationIssueSeverity,
9
9
  } from '@lota-sdk/shared'
10
+ import { HUMAN_NODE_TYPES, STRUCTURAL_NODE_TYPES } from '@lota-sdk/shared'
10
11
 
11
12
  import { isRecord } from '../utils/string'
12
13
  import { planCoordinationService } from './plan-coordination.service'
13
14
  import { readPathValue } from './plan-helpers'
15
+ import type { PlanNodeValidationSpec } from './plan-node-spec'
14
16
 
15
- const STRUCTURAL_NODE_TYPES = new Set(['switch', 'join', 'deliberation-fork'])
16
- const HUMAN_NODE_TYPES = new Set(['human-input', 'human-approval', 'human-review-edit', 'human-decision'])
17
+ const STRUCTURAL_NODE_TYPE_SET = new Set<PlanNodeType>(STRUCTURAL_NODE_TYPES)
18
+ const HUMAN_NODE_TYPE_SET = new Set<PlanNodeType>(HUMAN_NODE_TYPES)
17
19
 
18
20
  export interface PlanValidationIssueInput {
19
21
  severity: PlanValidationIssueSeverity
@@ -186,7 +188,7 @@ class PlanValidatorService {
186
188
  }
187
189
  nodeIds.add(node.id)
188
190
 
189
- const isStructuralNode = STRUCTURAL_NODE_TYPES.has(node.type)
191
+ const isStructuralNode = STRUCTURAL_NODE_TYPE_SET.has(node.type)
190
192
  if (!isStructuralNode && node.deliverables.length === 0) {
191
193
  blocking.push(
192
194
  createIssue({
@@ -214,7 +216,7 @@ class PlanValidatorService {
214
216
  }),
215
217
  )
216
218
  }
217
- if (HUMAN_NODE_TYPES.has(node.type) && node.owner.executorType !== 'user') {
219
+ if (HUMAN_NODE_TYPE_SET.has(node.type) && node.owner.executorType !== 'user') {
218
220
  blocking.push(
219
221
  createIssue({
220
222
  code: 'human_node_owner_mismatch',
@@ -224,7 +226,7 @@ class PlanValidatorService {
224
226
  )
225
227
  }
226
228
  if (
227
- !HUMAN_NODE_TYPES.has(node.type) &&
229
+ !HUMAN_NODE_TYPE_SET.has(node.type) &&
228
230
  node.type !== 'join' &&
229
231
  node.type !== 'switch' &&
230
232
  node.owner.ref.trim().length === 0
@@ -518,7 +520,7 @@ class PlanValidatorService {
518
520
 
519
521
  validateNodeResult(params: {
520
522
  draft: Pick<PlanDraft, 'schemas'>
521
- node: PlanNodeSpec
523
+ node: PlanNodeValidationSpec
522
524
  result: PlanNodeResultSubmission
523
525
  }): NodeResultValidationResult {
524
526
  const blocking: PlanValidationIssueInput[] = []
@@ -639,7 +641,7 @@ class PlanValidatorService {
639
641
 
640
642
  private evaluateCompletionCheck(params: {
641
643
  draft: Pick<PlanDraft, 'schemas'>
642
- node: PlanNodeSpec
644
+ node: PlanNodeValidationSpec
643
645
  result: PlanNodeResultSubmission
644
646
  check: PlanCompletionCheck
645
647
  }): PlanValidationIssueInput | null {
@@ -0,0 +1,2 @@
1
+ export const MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES = 15
2
+ export const MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES = 10
@@ -35,7 +35,7 @@ class WorkstreamTitleService {
35
35
  title = limitTitleWords(deriveTitle(sourceText || WORKSTREAM.DEFAULT_TITLE))
36
36
  }
37
37
 
38
- await workstreamService.persistGeneratedTitle(workstreamId, title)
38
+ await workstreamService.update(workstreamId, { title, nameGenerated: true })
39
39
  }
40
40
  }
41
41