@lota-sdk/core 0.2.3 → 0.3.0

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 (102) hide show
  1. package/infrastructure/schema/00_identity.surql +2 -2
  2. package/infrastructure/schema/00_thread.surql +75 -0
  3. package/infrastructure/schema/02_execution_plan.surql +10 -11
  4. package/infrastructure/schema/10_autonomous_job.surql +3 -3
  5. package/package.json +2 -2
  6. package/src/ai/definitions.ts +1 -1
  7. package/src/config/agent-defaults.ts +5 -5
  8. package/src/config/index.ts +1 -1
  9. package/src/config/thread-defaults.ts +72 -0
  10. package/src/create-runtime.ts +89 -93
  11. package/src/db/tables.ts +3 -3
  12. package/src/db/{workstream-message-row.ts → thread-message-row.ts} +3 -3
  13. package/src/queues/context-compaction.queue.ts +6 -6
  14. package/src/queues/plan-agent-heartbeat.queue.ts +3 -3
  15. package/src/queues/post-chat-memory.queue.ts +1 -1
  16. package/src/queues/title-generation.queue.ts +10 -13
  17. package/src/redis/index.ts +1 -1
  18. package/src/redis/stream-context.ts +1 -1
  19. package/src/runtime/agent-identity-overrides.ts +1 -1
  20. package/src/runtime/agent-runtime-policy.ts +19 -21
  21. package/src/runtime/chat-request-routing.ts +1 -1
  22. package/src/runtime/context-compaction-constants.ts +1 -1
  23. package/src/runtime/context-compaction.ts +1 -1
  24. package/src/runtime/execution-plan.ts +1 -1
  25. package/src/runtime/index.ts +1 -1
  26. package/src/runtime/memory-digest-policy.ts +1 -1
  27. package/src/runtime/plugin-types.ts +1 -1
  28. package/src/runtime/post-turn-side-effects.ts +35 -35
  29. package/src/runtime/runtime-config.ts +12 -12
  30. package/src/runtime/runtime-extensions.ts +11 -11
  31. package/src/runtime/social-chat-agent-runner.ts +3 -3
  32. package/src/runtime/social-chat-history.ts +1 -1
  33. package/src/runtime/social-chat.ts +6 -6
  34. package/src/runtime/team-consultation-orchestrator.ts +1 -1
  35. package/src/runtime/{workstream-chat-helpers.ts → thread-chat-helpers.ts} +7 -7
  36. package/src/runtime/{workstream-plan-turn.ts → thread-plan-turn.ts} +11 -17
  37. package/src/runtime/{workstream-turn-context.ts → thread-turn-context.ts} +10 -10
  38. package/src/services/agent-activity.service.ts +39 -44
  39. package/src/services/agent-executor.service.ts +17 -19
  40. package/src/services/attachment.service.ts +4 -8
  41. package/src/services/autonomous-job.service.ts +29 -28
  42. package/src/services/context-compaction.service.ts +19 -29
  43. package/src/services/execution-plan.service.ts +58 -70
  44. package/src/services/global-orchestrator.service.ts +5 -5
  45. package/src/services/index.ts +6 -6
  46. package/src/services/memory.service.ts +1 -1
  47. package/src/services/monitoring-window.service.ts +2 -2
  48. package/src/services/mutating-approval.service.ts +7 -10
  49. package/src/services/node-workspace.service.ts +8 -7
  50. package/src/services/notification.service.ts +1 -1
  51. package/src/services/organization.service.ts +9 -9
  52. package/src/services/ownership-dispatcher.service.ts +13 -19
  53. package/src/services/plan-agent-heartbeat.service.ts +13 -13
  54. package/src/services/plan-agent-query.service.ts +7 -7
  55. package/src/services/plan-artifact.service.ts +1 -2
  56. package/src/services/plan-coordination.service.ts +4 -4
  57. package/src/services/plan-cycle.service.ts +7 -7
  58. package/src/services/plan-deadline.service.ts +4 -4
  59. package/src/services/plan-event-delivery.service.ts +8 -12
  60. package/src/services/plan-executor.service.ts +16 -37
  61. package/src/services/plan-run-data.ts +27 -8
  62. package/src/services/plan-run.service.ts +7 -9
  63. package/src/services/plan-scheduler.service.ts +4 -4
  64. package/src/services/plan-template.service.ts +2 -2
  65. package/src/services/plan-validator.service.ts +0 -11
  66. package/src/services/plugin-executor.service.ts +1 -1
  67. package/src/services/queue-job.service.ts +1 -1
  68. package/src/services/recent-activity-title.service.ts +1 -1
  69. package/src/services/recent-activity.service.ts +4 -4
  70. package/src/services/system-executor.service.ts +2 -2
  71. package/src/services/{workstream-message.service.ts → thread-message.service.ts} +72 -76
  72. package/src/services/thread-plan-registry.service.ts +22 -0
  73. package/src/services/thread-title.service.ts +39 -0
  74. package/src/services/{workstream-turn-preparation.service.ts → thread-turn-preparation.service.ts} +131 -143
  75. package/src/services/{workstream-turn.ts → thread-turn.ts} +27 -31
  76. package/src/services/thread.service.ts +707 -0
  77. package/src/services/thread.types.ts +17 -0
  78. package/src/storage/attachment-storage.service.ts +4 -4
  79. package/src/system-agents/index.ts +1 -1
  80. package/src/system-agents/memory.agent.ts +1 -1
  81. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  82. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  83. package/src/system-agents/researcher.agent.ts +3 -3
  84. package/src/system-agents/{workstream-router.agent.ts → thread-router.agent.ts} +21 -21
  85. package/src/system-agents/title-generator.agent.ts +8 -8
  86. package/src/tools/execution-plan.tool.ts +39 -40
  87. package/src/tools/memory-block.tool.ts +4 -4
  88. package/src/tools/research-topic.tool.ts +1 -0
  89. package/src/tools/search-web.tool.ts +1 -1
  90. package/src/tools/search.tool.ts +4 -4
  91. package/src/tools/team-think.tool.ts +9 -9
  92. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  93. package/src/workers/regular-chat-memory-digest.runner.ts +43 -43
  94. package/src/workers/skill-extraction.runner.ts +9 -13
  95. package/src/workers/utils/{workstream-message-query.ts → thread-message-query.ts} +21 -21
  96. package/infrastructure/schema/00_workstream.surql +0 -64
  97. package/src/config/workstream-defaults.ts +0 -72
  98. package/src/services/workstream-plan-registry.service.ts +0 -22
  99. package/src/services/workstream-title.service.ts +0 -42
  100. package/src/services/workstream.service.ts +0 -803
  101. package/src/services/workstream.types.ts +0 -17
  102. /package/src/services/{workstream-constants.ts → thread-constants.ts} +0 -0
@@ -31,8 +31,8 @@ import type { PlanValidationIssueInput } from './plan-validator.service'
31
31
  import { pluginExecutorService } from './plugin-executor.service'
32
32
  import { skillResolverService } from './skill-resolver.service'
33
33
  import { systemExecutorService } from './system-executor.service'
34
+ import { ThreadSchema } from './thread.types'
34
35
  import { userService } from './user.service'
35
- import { WorkstreamSchema } from './workstream.types'
36
36
 
37
37
  const STABLE_RUN_STATUSES = new Set(['awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
38
38
  const MAX_DISPATCH_ITERATIONS = 64
@@ -73,8 +73,6 @@ function toArtifactSubmission(artifact: PlanArtifactRecord): PlanArtifactSubmiss
73
73
  return {
74
74
  name: artifact.name,
75
75
  kind: artifact.kind,
76
- pointer: artifact.pointer,
77
- ...(artifact.schemaRef ? { schemaRef: artifact.schemaRef } : {}),
78
76
  ...(artifact.description ? { description: artifact.description } : {}),
79
77
  ...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
80
78
  }
@@ -157,7 +155,7 @@ class OwnershipDispatcherService {
157
155
  if (spec.executionMode === 'graph-full') {
158
156
  const { globalOrchestratorService } = await import('./global-orchestrator.service')
159
157
  await globalOrchestratorService.routeGraphFull({
160
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
158
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
161
159
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
162
160
  })
163
161
  return this.serializeRun(run.id)
@@ -200,7 +198,7 @@ class OwnershipDispatcherService {
200
198
  })
201
199
 
202
200
  await planExecutorService.submitNodeResult({
203
- workstreamId: run.workstreamId,
201
+ threadId: run.threadId,
204
202
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
205
203
  nodeId: planNode.id,
206
204
  emittedBy: planNode.owner.ref,
@@ -208,7 +206,7 @@ class OwnershipDispatcherService {
208
206
  })
209
207
  } catch (error) {
210
208
  await planExecutorService.blockNodeOnDispatchFailure({
211
- workstreamId: run.workstreamId,
209
+ threadId: run.threadId,
212
210
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
213
211
  nodeId: planNode.id,
214
212
  emittedBy: planNode.owner.ref,
@@ -283,18 +281,14 @@ class OwnershipDispatcherService {
283
281
  nodeSpecRecord: PlanNodeSpecRecord,
284
282
  ): Promise<Omit<OwnershipDispatchContext, 'nodeId'>> {
285
283
  const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
286
- const workstreamId = recordIdToString(run.workstreamId, TABLES.WORKSTREAM)
284
+ const threadId = recordIdToString(run.threadId, TABLES.THREAD)
287
285
  const planId = recordIdToString(run.id, TABLES.PLAN_RUN)
288
- const [workstream, nodeSpecs, nodeRuns] = await Promise.all([
289
- databaseService.findOne(
290
- TABLES.WORKSTREAM,
291
- { id: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM) },
292
- WorkstreamSchema,
293
- ),
286
+ const [thread, nodeSpecs, nodeRuns] = await Promise.all([
287
+ databaseService.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema),
294
288
  planRunService.listNodeSpecs(spec.id),
295
289
  planRunService.listNodeRuns(run.id),
296
290
  ])
297
- const userId = workstream?.userId ? recordIdToString(workstream.userId, TABLES.USER) : undefined
291
+ const userId = thread?.userId ? recordIdToString(thread.userId, TABLES.USER) : undefined
298
292
  const userName = userId
299
293
  ? await userService
300
294
  .getUser(userId)
@@ -304,8 +298,7 @@ class OwnershipDispatcherService {
304
298
  const nodeSpecsById = new Map(nodeSpecs.map((candidate) => [candidate.nodeId, candidate]))
305
299
  const upstreamHandoffs: UpstreamHandoff[] = nodeRuns
306
300
  .filter(
307
- (candidate): candidate is typeof candidate & { handoffContext: NonNullable<typeof candidate.handoffContext> } =>
308
- nodeSpecRecord.upstreamNodeIds.includes(candidate.nodeId) && candidate.handoffContext !== undefined,
301
+ (candidate) => nodeSpecRecord.upstreamNodeIds.includes(candidate.nodeId) && candidate.latestNotes !== undefined,
309
302
  )
310
303
  .map((candidate) => {
311
304
  const upstreamNodeSpec = nodeSpecsById.get(candidate.nodeId)
@@ -314,13 +307,13 @@ class OwnershipDispatcherService {
314
307
  label: upstreamNodeSpec?.label ?? candidate.nodeId,
315
308
  ownerRef: upstreamNodeSpec?.owner.ref ?? 'unknown',
316
309
  ownerType: upstreamNodeSpec?.owner.executorType ?? 'system',
317
- handoffContext: candidate.handoffContext,
310
+ summary: candidate.latestNotes ?? undefined,
318
311
  }
319
312
  })
320
313
 
321
314
  return {
322
315
  organizationId,
323
- workstreamId,
316
+ threadId,
324
317
  planId,
325
318
  leadAgentId: run.leadAgentId,
326
319
  ...(userId ? { userId } : {}),
@@ -355,9 +348,10 @@ class OwnershipDispatcherService {
355
348
  nodeId: params.nodeSpec.id,
356
349
  config: params.nodeSpec.monitoringConfig,
357
350
  organizationId: params.context.organizationId,
358
- workstreamId: params.context.workstreamId,
351
+ threadId: params.context.threadId,
359
352
  })
360
353
  return {
354
+ notes: `Monitoring window started for node "${params.nodeSpec.label}".`,
361
355
  structuredOutput: { status: 'monitoring-started', config: params.nodeSpec.monitoringConfig },
362
356
  artifacts: [],
363
357
  }
@@ -6,7 +6,7 @@ import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-vi
6
6
  import { planAgentQueryService } from './plan-agent-query.service'
7
7
  import { planExecutorService } from './plan-executor.service'
8
8
  import { planRunService } from './plan-run.service'
9
- import { workstreamService } from './workstream.service'
9
+ import { threadService } from './thread.service'
10
10
 
11
11
  const PLAN_AGENT_HEARTBEAT_LOCK_TTL_MS = 300_000
12
12
  const PLAN_AGENT_HEARTBEAT_LOCK_REFRESH_MS = 20_000
@@ -17,26 +17,26 @@ function buildHeartbeatLockKey(runId: string, nodeId: string): string {
17
17
 
18
18
  function buildWakeDedupeKey(params: {
19
19
  organizationId: string
20
- workstreamId: string
20
+ threadId: string
21
21
  runId: string
22
22
  nodeId: string
23
23
  agentId: string
24
24
  }) {
25
- return `${params.organizationId}:${params.workstreamId}:${params.runId}:${params.nodeId}:${params.agentId}`
25
+ return `${params.organizationId}:${params.threadId}:${params.runId}:${params.nodeId}:${params.agentId}`
26
26
  }
27
27
 
28
28
  class PlanAgentHeartbeatService {
29
29
  async wakeNode(params: {
30
30
  organizationId: string
31
- workstreamId: string
31
+ threadId: string
32
32
  runId: string
33
33
  nodeId: string
34
34
  agentId: string
35
35
  reason: string
36
36
  }): Promise<boolean> {
37
- const workstreamRef = ensureRecordId(params.workstreamId, TABLES.WORKSTREAM)
38
- await workstreamService.clearStaleActiveRunIfMissingFromRegistry(workstreamRef)
39
- if (await workstreamService.getActiveRunId(workstreamRef)) {
37
+ const threadRef = ensureRecordId(params.threadId, TABLES.THREAD)
38
+ await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
39
+ if (await threadService.getActiveRunId(threadRef)) {
40
40
  return false
41
41
  }
42
42
 
@@ -51,11 +51,11 @@ class PlanAgentHeartbeatService {
51
51
  logger: serverLogger,
52
52
  },
53
53
  async () => {
54
- await workstreamService.clearStaleActiveRunIfMissingFromRegistry(workstreamRef)
55
- if (await workstreamService.hasActiveRunLease(workstreamRef)) {
54
+ await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
55
+ if (await threadService.hasActiveRunLease(threadRef)) {
56
56
  return false
57
57
  }
58
- if (await workstreamService.getActiveRunId(workstreamRef)) {
58
+ if (await threadService.getActiveRunId(threadRef)) {
59
59
  return false
60
60
  }
61
61
 
@@ -83,7 +83,7 @@ class PlanAgentHeartbeatService {
83
83
  await planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId })
84
84
  }
85
85
 
86
- const { triggerPlanNodeTurn } = await import('./workstream-turn')
86
+ const { triggerPlanNodeTurn } = await import('./thread-turn')
87
87
 
88
88
  await triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId })
89
89
  return true
@@ -101,7 +101,7 @@ class PlanAgentHeartbeatService {
101
101
 
102
102
  const wakeTargets = new Map<
103
103
  string,
104
- { organizationId: string; workstreamId: string; runId: string; nodeId: string; agentId: string; reason: string }
104
+ { organizationId: string; threadId: string; runId: string; nodeId: string; agentId: string; reason: string }
105
105
  >()
106
106
 
107
107
  for (const node of actionable) {
@@ -118,7 +118,7 @@ class PlanAgentHeartbeatService {
118
118
  }
119
119
  const wakeTarget = {
120
120
  organizationId: node.organizationId,
121
- workstreamId: node.workstreamId,
121
+ threadId: node.threadId,
122
122
  runId: node.runId,
123
123
  nodeId: node.nodeId,
124
124
  agentId: node.agentId,
@@ -15,7 +15,7 @@ const DEADLINE_TRACKED_NODE_STATUSES = new Set(['ready', 'running', 'awaiting-hu
15
15
 
16
16
  export interface ActionablePlanAgentNode {
17
17
  organizationId: string
18
- workstreamId: string
18
+ threadId: string
19
19
  runId: string
20
20
  nodeId: string
21
21
  agentId: string
@@ -25,7 +25,7 @@ export interface ActionablePlanAgentNode {
25
25
 
26
26
  export interface ApproachingDeadlineNode {
27
27
  organizationId: string
28
- workstreamId: string
28
+ threadId: string
29
29
  runId: string
30
30
  nodeId: string
31
31
  agentId?: string
@@ -37,7 +37,7 @@ export interface ApproachingDeadlineNode {
37
37
 
38
38
  export interface RecentlyUnblockedNode {
39
39
  organizationId: string
40
- workstreamId: string
40
+ threadId: string
41
41
  runId: string
42
42
  nodeId: string
43
43
  agentId: string
@@ -89,7 +89,7 @@ class PlanAgentQueryService {
89
89
  if (params.agentId && params.agentId !== visibleTarget.agentId) continue
90
90
  actionable.push({
91
91
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
92
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
92
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
93
93
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
94
94
  nodeId: nodeSpec.nodeId,
95
95
  agentId: visibleTarget.agentId,
@@ -124,7 +124,7 @@ class PlanAgentQueryService {
124
124
 
125
125
  actionable.push({
126
126
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
127
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
127
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
128
128
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
129
129
  nodeId: nodeSpec.nodeId,
130
130
  agentId: visibleTarget.agentId,
@@ -176,7 +176,7 @@ class PlanAgentQueryService {
176
176
  const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
177
177
  matches.push({
178
178
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
179
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
179
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
180
180
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
181
181
  nodeId: nodeSpec.nodeId,
182
182
  ...(visibleTarget ? { agentId: visibleTarget.agentId, visibility: visibleTarget.visibility } : {}),
@@ -232,7 +232,7 @@ class PlanAgentQueryService {
232
232
 
233
233
  matches.push({
234
234
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
235
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
235
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
236
236
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
237
237
  nodeId: currentNodeId,
238
238
  agentId: visibleTarget.agentId,
@@ -28,8 +28,7 @@ class PlanArtifactService {
28
28
  nodeId: params.nodeId,
29
29
  name: artifact.name,
30
30
  kind: artifact.kind,
31
- pointer: artifact.pointer,
32
- ...(artifact.schemaRef ? { schemaRef: artifact.schemaRef } : {}),
31
+ pointer: `artifact://${params.nodeId}/${artifact.name}`,
33
32
  ...(artifact.description ? { description: artifact.description } : {}),
34
33
  ...(artifact.payload ? { payload: artifact.payload } : {}),
35
34
  })
@@ -13,7 +13,7 @@ class PlanCoordinationService {
13
13
  /**
14
14
  * Resolve cross-plan artifact dependencies.
15
15
  * For each dependency:
16
- * 1. Find the source plan by title in the workstream
16
+ * 1. Find the source plan by title in the thread
17
17
  * 2. Find the artifact by (nodeId, artifactName) in that plan's run
18
18
  * 3. Check staleness if maxStalenessMs set
19
19
  * 4. Based on triggerMode:
@@ -23,7 +23,7 @@ class PlanCoordinationService {
23
23
  */
24
24
  async resolveDependencies(params: {
25
25
  dependencies: PlanDependency[]
26
- workstreamId: string
26
+ threadId: string
27
27
  }): Promise<DependencyResolutionResult> {
28
28
  const { planRunService } = await import('./plan-run.service')
29
29
 
@@ -34,10 +34,10 @@ class PlanCoordinationService {
34
34
  for (const dep of params.dependencies) {
35
35
  const depKey = `${dep.sourcePlanTitle}:${dep.sourceNodeId}:${dep.artifactName}`
36
36
 
37
- const specs = await planRunService.listPlanSpecsByWorkstream(params.workstreamId)
37
+ const specs = await planRunService.listPlanSpecsByThread(params.threadId)
38
38
  const sourceSpec = specs.find((s) => s.title === dep.sourcePlanTitle)
39
39
  if (!sourceSpec) {
40
- const reason = `Source plan "${dep.sourcePlanTitle}" not found in workstream.`
40
+ const reason = `Source plan "${dep.sourcePlanTitle}" not found in thread.`
41
41
  if (dep.triggerMode === 'block') {
42
42
  unresolved.push(dep)
43
43
  } else if (dep.triggerMode === 'notify') {
@@ -64,7 +64,7 @@ class PlanCycleService {
64
64
 
65
65
  async createCycle(params: {
66
66
  organizationId: RecordIdInput
67
- workstreamId: RecordIdInput
67
+ threadId: RecordIdInput
68
68
  templateId: RecordIdInput
69
69
  name: string
70
70
  schedule: CycleSchedule
@@ -77,7 +77,7 @@ class PlanCycleService {
77
77
  TABLES.PLAN_CYCLE,
78
78
  {
79
79
  organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
80
- workstreamId: ensureRecordId(params.workstreamId, TABLES.WORKSTREAM),
80
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
81
81
  templateId: ensureRecordId(params.templateId, TABLES.PLAN_TEMPLATE),
82
82
  name: params.name,
83
83
  schedule: params.schedule,
@@ -92,7 +92,7 @@ class PlanCycleService {
92
92
  const createResult = await planTemplateService.instantiate({
93
93
  templateId: params.templateId,
94
94
  organizationId: params.organizationId,
95
- workstreamId: params.workstreamId,
95
+ threadId: params.threadId,
96
96
  leadAgentId: params.leadAgentId,
97
97
  })
98
98
 
@@ -101,7 +101,7 @@ class PlanCycleService {
101
101
  const scheduleSpec = cycleScheduleToSpec(params.schedule)
102
102
  const scheduleRecord = await planSchedulerService.createSchedule({
103
103
  organizationId: params.organizationId,
104
- workstreamId: params.workstreamId,
104
+ threadId: params.threadId,
105
105
  ...(createdRunId ? { runId: ensureRecordId(createdRunId, TABLES.PLAN_RUN) } : {}),
106
106
  scheduleSpec,
107
107
  })
@@ -160,7 +160,7 @@ class PlanCycleService {
160
160
  const result = await planTemplateService.instantiate({
161
161
  templateId: cycle.templateId,
162
162
  organizationId: cycle.organizationId,
163
- workstreamId: cycle.workstreamId,
163
+ threadId: cycle.threadId,
164
164
  leadAgentId: 'system',
165
165
  overrides: draft,
166
166
  })
@@ -263,10 +263,10 @@ class PlanCycleService {
263
263
  )
264
264
  }
265
265
 
266
- async listCycles(workstreamId: RecordIdInput): Promise<PlanCycleRecord[]> {
266
+ async listCycles(threadId: RecordIdInput): Promise<PlanCycleRecord[]> {
267
267
  return databaseService.findMany(
268
268
  TABLES.PLAN_CYCLE,
269
- { workstreamId: ensureRecordId(workstreamId, TABLES.WORKSTREAM) },
269
+ { threadId: ensureRecordId(threadId, TABLES.THREAD) },
270
270
  PlanCycleRecordSchema,
271
271
  { orderBy: 'createdAt', orderDir: 'ASC' },
272
272
  )
@@ -126,7 +126,7 @@ class PlanDeadlineService {
126
126
  await this.applyDeadlineMissAction({
127
127
  runId: entry.nodeRun.runId,
128
128
  nodeId: entry.nodeRun.nodeId,
129
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
129
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
130
130
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
131
131
  action: deadline.missAction,
132
132
  emittedBy: 'plan-deadline-checker',
@@ -228,7 +228,7 @@ class PlanDeadlineService {
228
228
  async applyDeadlineMissAction(params: {
229
229
  runId: RecordIdInput
230
230
  nodeId: string
231
- workstreamId: string
231
+ threadId: string
232
232
  organizationId: string
233
233
  action: DeadlineAction
234
234
  emittedBy: string
@@ -275,7 +275,7 @@ class PlanDeadlineService {
275
275
  })
276
276
  const { planExecutorService } = await import('./plan-executor.service')
277
277
  await planExecutorService.blockNodeOnDispatchFailure({
278
- workstreamId: params.workstreamId,
278
+ threadId: params.threadId,
279
279
  runId: runIdStr,
280
280
  nodeId: params.nodeId,
281
281
  emittedBy: params.emittedBy,
@@ -296,7 +296,7 @@ class PlanDeadlineService {
296
296
  })
297
297
  const { planExecutorService } = await import('./plan-executor.service')
298
298
  await planExecutorService.blockNodeOnDispatchFailure({
299
- workstreamId: params.workstreamId,
299
+ threadId: params.threadId,
300
300
  runId: runIdStr,
301
301
  nodeId: params.nodeId,
302
302
  emittedBy: params.emittedBy,
@@ -9,8 +9,8 @@ import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-vi
9
9
  import { getRuntimeAdapters } from '../runtime/runtime-extensions'
10
10
  import type { LotaRuntimePlanEventEnvelope } from '../runtime/runtime-extensions'
11
11
  import { planRunService } from './plan-run.service'
12
+ import { ThreadSchema } from './thread.types'
12
13
  import { userService } from './user.service'
13
- import { WorkstreamSchema } from './workstream.types'
14
14
 
15
15
  const PLAN_EVENT_DELIVERED_TTL_MS = 7 * 24 * 60 * 60 * 1000
16
16
 
@@ -41,24 +41,20 @@ class PlanEventDeliveryService {
41
41
  private async deliverEvent(rawEvent: PlanEventRecord): Promise<void> {
42
42
  const event = PlanEventSchema.parse(rawEvent)
43
43
  const run = await planRunService.getRunById(event.runId)
44
- const [spec, nodeSpecs, nodeRuns, workstream] = await Promise.all([
44
+ const [spec, nodeSpecs, nodeRuns, thread] = await Promise.all([
45
45
  planRunService.getPlanSpecById(run.planSpecId),
46
46
  planRunService.listNodeSpecs(run.planSpecId),
47
47
  planRunService.listNodeRuns(run.id),
48
- databaseService.findOne(
49
- TABLES.WORKSTREAM,
50
- { id: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM) },
51
- WorkstreamSchema,
52
- ),
48
+ databaseService.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema),
53
49
  ])
54
50
  const nodeSpecsById = new Map(nodeSpecs.map((nodeSpec) => [nodeSpec.nodeId, nodeSpec]))
55
51
  const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
56
52
  const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
57
- const workstreamId = recordIdToString(run.workstreamId, TABLES.WORKSTREAM)
53
+ const threadId = recordIdToString(run.threadId, TABLES.THREAD)
58
54
  const runIdString = recordIdToString(run.id, TABLES.PLAN_RUN)
59
55
  const planSpecId = recordIdToString(spec.id, TABLES.PLAN_SPEC)
60
- const userId = workstream?.userId
61
- ? recordIdToString(ensureRecordId(workstream.userId, TABLES.USER), TABLES.USER)
56
+ const userId = thread?.userId
57
+ ? recordIdToString(ensureRecordId(thread.userId, TABLES.USER), TABLES.USER)
62
58
  : undefined
63
59
  const userName =
64
60
  userId === undefined
@@ -75,7 +71,7 @@ class PlanEventDeliveryService {
75
71
  ...(event.nodeId ? { nodeSpec: nodeSpecsById.get(event.nodeId) } : {}),
76
72
  ...(event.nodeId ? { nodeRun: nodeRunsById.get(event.nodeId) } : {}),
77
73
  organizationId,
78
- workstreamId,
74
+ threadId,
79
75
  runId: runIdString,
80
76
  planSpecId,
81
77
  ...(userId ? { userId } : {}),
@@ -123,7 +119,7 @@ class PlanEventDeliveryService {
123
119
  const { enqueuePlanAgentHeartbeatWake } = await import('../queues/plan-agent-heartbeat.queue')
124
120
  await enqueuePlanAgentHeartbeatWake({
125
121
  organizationId: envelope.organizationId,
126
- workstreamId: envelope.workstreamId,
122
+ threadId: envelope.threadId,
127
123
  runId: envelope.runId,
128
124
  nodeId: wakeTarget.nodeId,
129
125
  agentId: wakeTarget.agentId,
@@ -92,20 +92,15 @@ function parseLiteralValue(raw: string): unknown {
92
92
  }
93
93
  }
94
94
 
95
- function buildArtifactContext(
96
- artifacts: Array<{ name: string; kind: string; pointer: string; schemaRef?: string; payload?: unknown }>,
97
- ) {
95
+ function buildArtifactContext(artifacts: Array<{ name: string; kind: string; payload?: unknown }>) {
98
96
  return Object.fromEntries(
99
- artifacts.map((artifact) => [
100
- artifact.name,
101
- { kind: artifact.kind, pointer: artifact.pointer, schemaRef: artifact.schemaRef, payload: artifact.payload },
102
- ]),
97
+ artifacts.map((artifact) => [artifact.name, { kind: artifact.kind, payload: artifact.payload }]),
103
98
  )
104
99
  }
105
100
 
106
101
  function buildNodeContext(params: {
107
102
  nodeRun: PlanNodeRunRecord | undefined
108
- artifacts: Array<{ name: string; kind: string; pointer: string; schemaRef?: string; payload?: unknown }>
103
+ artifacts: Array<{ name: string; kind: string; payload?: unknown }>
109
104
  }) {
110
105
  return {
111
106
  input: params.nodeRun?.resolvedInput ?? {},
@@ -159,7 +154,6 @@ type PlanNodeRunUpdate = Omit<
159
154
  | 'resolvedInput'
160
155
  | 'latestStructuredOutput'
161
156
  | 'latestNotes'
162
- | 'handoffContext'
163
157
  | 'latestAttemptId'
164
158
  | 'scheduledAt'
165
159
  | 'readyAt'
@@ -171,7 +165,6 @@ type PlanNodeRunUpdate = Omit<
171
165
  resolvedInput?: Record<string, unknown> | null
172
166
  latestStructuredOutput?: Record<string, unknown> | null
173
167
  latestNotes?: string | null
174
- handoffContext?: Record<string, unknown> | null
175
168
  latestAttemptId?: RecordIdInput | null
176
169
  scheduledAt?: string | Date | null
177
170
  readyAt?: string | Date | null
@@ -208,13 +201,6 @@ function toNodeRunData(nodeRun: PlanNodeRunRecord, patch: PlanNodeRunUpdate) {
208
201
  : nodeRun.latestNotes
209
202
  ? { latestNotes: nodeRun.latestNotes }
210
203
  : {}),
211
- ...(patch.handoffContext === null
212
- ? {}
213
- : patch.handoffContext !== undefined
214
- ? { handoffContext: patch.handoffContext }
215
- : nodeRun.handoffContext
216
- ? { handoffContext: nodeRun.handoffContext }
217
- : {}),
218
204
  ...(patch.latestAttemptId === null
219
205
  ? {}
220
206
  : patch.latestAttemptId !== undefined
@@ -280,17 +266,15 @@ function deriveApprovalStatus(response: Record<string, unknown>): 'approved' | '
280
266
 
281
267
  class PlanExecutorService {
282
268
  async submitNodeResult(params: {
283
- workstreamId: RecordIdInput
269
+ threadId: RecordIdInput
284
270
  runId: string
285
271
  nodeId: string
286
272
  emittedBy: string
287
273
  result: PlanNodeResultSubmission
288
274
  }): Promise<ExecutionPlanToolResultData> {
289
275
  const run = await planRunService.getRunById(params.runId)
290
- if (
291
- recordIdToString(run.workstreamId, TABLES.WORKSTREAM) !== recordIdToString(params.workstreamId, TABLES.WORKSTREAM)
292
- ) {
293
- throw new Error('Execution node result targets a different workstream.')
276
+ if (recordIdToString(run.threadId, TABLES.THREAD) !== recordIdToString(params.threadId, TABLES.THREAD)) {
277
+ throw new Error('Execution node result targets a different thread.')
294
278
  }
295
279
  if (run.status === 'completed' || run.status === 'failed' || run.status === 'aborted') {
296
280
  throw new Error('Execution run is no longer active.')
@@ -375,8 +359,7 @@ class PlanExecutorService {
375
359
  attemptCount: nodeRun.attemptCount + 1,
376
360
  latestAttemptId: finalizedAttempt.id,
377
361
  latestStructuredOutput: params.result.structuredOutput ?? null,
378
- latestNotes: params.result.notes ?? null,
379
- handoffContext: params.result.handoffContext ?? null,
362
+ latestNotes: params.result.notes,
380
363
  }),
381
364
  )
382
365
  .output('after'),
@@ -659,8 +642,7 @@ class PlanExecutorService {
659
642
  status: validation.warnings.length > 0 ? 'partial' : 'completed',
660
643
  latestAttemptId: finalizedAttempt.id,
661
644
  latestStructuredOutput: params.result.structuredOutput ?? null,
662
- latestNotes: params.result.notes ?? null,
663
- handoffContext: params.result.handoffContext ?? null,
645
+ latestNotes: params.result.notes,
664
646
  blockedReason: null,
665
647
  failureClass: null,
666
648
  completedAt: new Date(),
@@ -751,18 +733,17 @@ class PlanExecutorService {
751
733
  action: 'node-result-submitted',
752
734
  plan: snapshot,
753
735
  message: `Submitted result for node "${nodeSpec.label}".`,
754
- changedNodeId: params.nodeId,
755
736
  })
756
737
  }
757
738
 
758
739
  async submitHumanNodeResponse(params: {
759
- workstreamId: RecordIdInput
740
+ threadId: RecordIdInput
760
741
  approvalId?: string
761
742
  respondedBy: string
762
743
  response: Record<string, unknown>
763
744
  approvalMessageId?: string
764
745
  }): Promise<SerializableExecutionPlan | null> {
765
- const run = await planRunService.getActiveRunRecord(params.workstreamId)
746
+ const run = await planRunService.getActiveRunRecord(params.threadId)
766
747
  if (!run || run.status !== 'awaiting-human' || !run.waitingNodeId) {
767
748
  return null
768
749
  }
@@ -790,7 +771,7 @@ class PlanExecutorService {
790
771
  result: {
791
772
  structuredOutput: params.response,
792
773
  artifacts: [],
793
- notes: typeof params.response.comments === 'string' ? params.response.comments : undefined,
774
+ notes: typeof params.response.comments === 'string' ? params.response.comments : 'Human response submitted.',
794
775
  },
795
776
  })
796
777
  const emittedEvents: PlanEventRecord[] = []
@@ -818,7 +799,7 @@ class PlanExecutorService {
818
799
  result: {
819
800
  structuredOutput: params.response,
820
801
  artifacts: [],
821
- notes: typeof params.response.comments === 'string' ? params.response.comments : undefined,
802
+ notes: typeof params.response.comments === 'string' ? params.response.comments : 'Human response submitted.',
822
803
  },
823
804
  status: validation.blocking.length > 0 ? 'failed' : 'completed',
824
805
  failureClass: validation.failureClass,
@@ -860,7 +841,6 @@ class PlanExecutorService {
860
841
  latestAttemptId: attempt.id,
861
842
  latestStructuredOutput: params.response,
862
843
  latestNotes: typeof params.response.comments === 'string' ? params.response.comments : null,
863
- handoffContext: null,
864
844
  blockedReason: validation.blocking[0]?.message ?? null,
865
845
  failureClass: validation.failureClass,
866
846
  }),
@@ -877,7 +857,6 @@ class PlanExecutorService {
877
857
  latestAttemptId: attempt.id,
878
858
  latestStructuredOutput: params.response,
879
859
  latestNotes: typeof params.response.comments === 'string' ? params.response.comments : null,
880
- handoffContext: null,
881
860
  blockedReason: null,
882
861
  failureClass: null,
883
862
  completedAt: new Date(),
@@ -981,7 +960,7 @@ class PlanExecutorService {
981
960
  }
982
961
 
983
962
  async resumeRun(params: {
984
- workstreamId: RecordIdInput
963
+ threadId: RecordIdInput
985
964
  runId: string
986
965
  emittedBy: string
987
966
  }): Promise<ExecutionPlanToolResultData> {
@@ -1106,7 +1085,7 @@ class PlanExecutorService {
1106
1085
  }
1107
1086
 
1108
1087
  async blockNodeOnDispatchFailure(params: {
1109
- workstreamId: RecordIdInput
1088
+ threadId: RecordIdInput
1110
1089
  runId: string
1111
1090
  nodeId: string
1112
1091
  emittedBy: string
@@ -1297,7 +1276,7 @@ class PlanExecutorService {
1297
1276
  if (params.spec.dependencies && params.spec.dependencies.length > 0) {
1298
1277
  const { unresolved } = await planCoordinationService.resolveDependencies({
1299
1278
  dependencies: params.spec.dependencies,
1300
- workstreamId: recordIdToString(params.spec.workstreamId, TABLES.WORKSTREAM),
1279
+ threadId: recordIdToString(params.spec.threadId, TABLES.THREAD),
1301
1280
  })
1302
1281
  if (unresolved.length > 0) {
1303
1282
  currentRun = await this.replaceRun(params.tx, currentRun, { status: 'blocked', readyNodeIds: [] })
@@ -1405,7 +1384,7 @@ class PlanExecutorService {
1405
1384
  replaceNodeRun(scheduledNodeRun)
1406
1385
  await planSchedulerService.createSchedule({
1407
1386
  organizationId: currentRun.organizationId,
1408
- workstreamId: currentRun.workstreamId,
1387
+ threadId: currentRun.threadId,
1409
1388
  planSpecId: params.spec.id,
1410
1389
  runId: currentRun.id,
1411
1390
  nodeId: nodeSpec.nodeId,