@lota-sdk/core 0.2.3 → 0.3.1

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 (106) hide show
  1. package/infrastructure/schema/00_identity.surql +2 -2
  2. package/infrastructure/schema/00_thread.surql +73 -0
  3. package/infrastructure/schema/02_execution_plan.surql +10 -11
  4. package/infrastructure/schema/04_runtime_bootstrap.surql +1 -0
  5. package/infrastructure/schema/10_autonomous_job.surql +3 -3
  6. package/package.json +2 -2
  7. package/src/ai/definitions.ts +1 -1
  8. package/src/config/agent-defaults.ts +5 -5
  9. package/src/config/index.ts +1 -1
  10. package/src/config/thread-defaults.ts +72 -0
  11. package/src/create-runtime.ts +90 -94
  12. package/src/db/record-id.ts +21 -21
  13. package/src/db/service.ts +44 -40
  14. package/src/db/tables.ts +3 -3
  15. package/src/db/{workstream-message-row.ts → thread-message-row.ts} +3 -3
  16. package/src/queues/context-compaction.queue.ts +6 -6
  17. package/src/queues/plan-agent-heartbeat.queue.ts +3 -3
  18. package/src/queues/post-chat-memory.queue.ts +1 -1
  19. package/src/queues/title-generation.queue.ts +10 -13
  20. package/src/redis/index.ts +1 -1
  21. package/src/redis/stream-context.ts +1 -1
  22. package/src/runtime/agent-identity-overrides.ts +1 -1
  23. package/src/runtime/agent-runtime-policy.ts +19 -21
  24. package/src/runtime/chat-request-routing.ts +1 -1
  25. package/src/runtime/context-compaction-constants.ts +1 -1
  26. package/src/runtime/context-compaction.ts +1 -1
  27. package/src/runtime/execution-plan.ts +1 -1
  28. package/src/runtime/index.ts +1 -1
  29. package/src/runtime/memory-digest-policy.ts +1 -1
  30. package/src/runtime/plugin-types.ts +1 -1
  31. package/src/runtime/post-turn-side-effects.ts +35 -35
  32. package/src/runtime/runtime-config.ts +24 -21
  33. package/src/runtime/runtime-extensions.ts +11 -11
  34. package/src/runtime/social-chat-agent-runner.ts +3 -3
  35. package/src/runtime/social-chat-history.ts +1 -1
  36. package/src/runtime/social-chat.ts +6 -6
  37. package/src/runtime/team-consultation-orchestrator.ts +1 -1
  38. package/src/runtime/{workstream-chat-helpers.ts → thread-chat-helpers.ts} +7 -7
  39. package/src/runtime/{workstream-plan-turn.ts → thread-plan-turn.ts} +11 -17
  40. package/src/runtime/{workstream-turn-context.ts → thread-turn-context.ts} +10 -10
  41. package/src/services/agent-activity.service.ts +39 -44
  42. package/src/services/agent-executor.service.ts +17 -19
  43. package/src/services/attachment.service.ts +4 -8
  44. package/src/services/autonomous-job.service.ts +29 -28
  45. package/src/services/context-compaction.service.ts +19 -29
  46. package/src/services/execution-plan.service.ts +58 -70
  47. package/src/services/global-orchestrator.service.ts +5 -5
  48. package/src/services/index.ts +6 -6
  49. package/src/services/memory.service.ts +1 -1
  50. package/src/services/monitoring-window.service.ts +2 -2
  51. package/src/services/mutating-approval.service.ts +7 -10
  52. package/src/services/node-workspace.service.ts +8 -7
  53. package/src/services/notification.service.ts +1 -1
  54. package/src/services/organization.service.ts +9 -9
  55. package/src/services/ownership-dispatcher.service.ts +13 -19
  56. package/src/services/plan-agent-heartbeat.service.ts +13 -13
  57. package/src/services/plan-agent-query.service.ts +7 -7
  58. package/src/services/plan-artifact.service.ts +1 -2
  59. package/src/services/plan-coordination.service.ts +4 -4
  60. package/src/services/plan-cycle.service.ts +7 -7
  61. package/src/services/plan-deadline.service.ts +4 -4
  62. package/src/services/plan-event-delivery.service.ts +8 -12
  63. package/src/services/plan-executor.service.ts +25 -39
  64. package/src/services/plan-run-data.ts +27 -8
  65. package/src/services/plan-run.service.ts +7 -9
  66. package/src/services/plan-scheduler.service.ts +4 -4
  67. package/src/services/plan-template.service.ts +2 -2
  68. package/src/services/plan-validator.service.ts +0 -11
  69. package/src/services/plugin-executor.service.ts +1 -1
  70. package/src/services/queue-job.service.ts +1 -1
  71. package/src/services/recent-activity-title.service.ts +1 -1
  72. package/src/services/recent-activity.service.ts +4 -4
  73. package/src/services/system-executor.service.ts +2 -2
  74. package/src/services/{workstream-message.service.ts → thread-message.service.ts} +72 -76
  75. package/src/services/thread-plan-registry.service.ts +22 -0
  76. package/src/services/thread-title.service.ts +39 -0
  77. package/src/services/{workstream-turn-preparation.service.ts → thread-turn-preparation.service.ts} +148 -171
  78. package/src/services/{workstream-turn.ts → thread-turn.ts} +27 -31
  79. package/src/services/thread.service.ts +853 -0
  80. package/src/services/thread.types.ts +17 -0
  81. package/src/storage/attachment-storage.service.ts +4 -4
  82. package/src/system-agents/index.ts +1 -1
  83. package/src/system-agents/memory.agent.ts +1 -1
  84. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  85. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  86. package/src/system-agents/researcher.agent.ts +3 -3
  87. package/src/system-agents/{workstream-router.agent.ts → thread-router.agent.ts} +68 -135
  88. package/src/system-agents/title-generator.agent.ts +8 -8
  89. package/src/tools/execution-plan.tool.ts +39 -40
  90. package/src/tools/memory-block.tool.ts +4 -4
  91. package/src/tools/research-topic.tool.ts +1 -0
  92. package/src/tools/search-web.tool.ts +1 -1
  93. package/src/tools/search.tool.ts +4 -4
  94. package/src/tools/team-think.tool.ts +9 -9
  95. package/src/utils/async.ts +6 -7
  96. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  97. package/src/workers/regular-chat-memory-digest.runner.ts +43 -43
  98. package/src/workers/skill-extraction.runner.ts +9 -13
  99. package/src/workers/utils/{workstream-message-query.ts → thread-message-query.ts} +21 -21
  100. package/infrastructure/schema/00_workstream.surql +0 -64
  101. package/src/config/workstream-defaults.ts +0 -72
  102. package/src/services/workstream-plan-registry.service.ts +0 -22
  103. package/src/services/workstream-title.service.ts +0 -42
  104. package/src/services/workstream.service.ts +0 -803
  105. package/src/services/workstream.types.ts +0 -17
  106. /package/src/services/{workstream-constants.ts → thread-constants.ts} +0 -0
@@ -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,
@@ -21,7 +21,7 @@ import {
21
21
  STRUCTURAL_NODE_TYPES as STRUCTURAL_NODE_TYPE_VALUES,
22
22
  PlanValidationIssueSchema,
23
23
  } from '@lota-sdk/shared'
24
- import { RecordId } from 'surrealdb'
24
+ import { RecordId, StringRecordId } from 'surrealdb'
25
25
 
26
26
  import { aiLogger } from '../config/logger'
27
27
  import type { RecordIdInput } from '../db/record-id'
@@ -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,
@@ -1886,7 +1865,14 @@ class PlanExecutorService {
1886
1865
  run: PlanRunRecord,
1887
1866
  checkpoint: RecordIdInput | { id: RecordIdInput },
1888
1867
  ) {
1889
- const checkpointId = checkpoint && typeof checkpoint === 'object' && 'id' in checkpoint ? checkpoint.id : checkpoint
1868
+ const checkpointId =
1869
+ checkpoint &&
1870
+ typeof checkpoint === 'object' &&
1871
+ !(checkpoint instanceof RecordId) &&
1872
+ !(checkpoint instanceof StringRecordId) &&
1873
+ 'id' in checkpoint
1874
+ ? (checkpoint as { id: RecordIdInput }).id
1875
+ : checkpoint
1890
1876
 
1891
1877
  await tx
1892
1878
  .update(ensureRecordId(run.id, TABLES.PLAN_RUN))
@@ -1,4 +1,4 @@
1
- import type { ExecutionPlanToolResultData, PlanRunRecord } from '@lota-sdk/shared'
1
+ import type { ExecutionPlanToolResultData, PlanRunRecord, SerializableExecutionPlan } from '@lota-sdk/shared'
2
2
 
3
3
  import type { RecordIdInput } from '../db/record-id'
4
4
  import { ensureRecordId } from '../db/record-id'
@@ -21,7 +21,7 @@ export function toRunData(run: PlanRunRecord, patch: PlanRunUpdate) {
21
21
  return {
22
22
  planSpecId: ensureRecordId(run.planSpecId, TABLES.PLAN_SPEC),
23
23
  organizationId: ensureRecordId(run.organizationId, TABLES.ORGANIZATION),
24
- workstreamId: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM),
24
+ threadId: ensureRecordId(run.threadId, TABLES.THREAD),
25
25
  leadAgentId: patch.leadAgentId ?? run.leadAgentId,
26
26
  status: patch.status ?? run.status,
27
27
  ...(patch.currentNodeId === null
@@ -71,18 +71,37 @@ export function toRunData(run: PlanRunRecord, patch: PlanRunUpdate) {
71
71
  }
72
72
  }
73
73
 
74
+ function toSlimPlanSummary(plan: SerializableExecutionPlan): NonNullable<ExecutionPlanToolResultData['plan']> {
75
+ const completed = plan.progress.completed + plan.progress.partial
76
+ return {
77
+ runId: plan.runId,
78
+ title: plan.title,
79
+ objective: plan.objective,
80
+ status: plan.status,
81
+ progress: { completed, total: plan.progress.total },
82
+ nodes: plan.nodes.map((node) => ({
83
+ id: node.id,
84
+ label: node.label,
85
+ type: node.type,
86
+ status: node.status,
87
+ ownerRef: node.owner.ref,
88
+ })),
89
+ activeNodeIds: plan.activeNodeIds,
90
+ readyNodeIds: plan.readyNodeIds,
91
+ }
92
+ }
93
+
74
94
  export function buildExecutionPlanToolResult(params: {
75
95
  action: ExecutionPlanToolResultData['action']
76
- plan: ExecutionPlanToolResultData['plan'] | null
96
+ plan: SerializableExecutionPlan | null
77
97
  message: string
78
- changedNodeId?: string
79
98
  }): ExecutionPlanToolResultData {
99
+ const slim = params.plan ? toSlimPlanSummary(params.plan) : null
80
100
  return {
81
101
  action: params.action,
82
102
  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,
103
+ ...(slim ? { plan: slim } : {}),
104
+ hasPlan: slim !== null,
105
+ status: slim?.status,
87
106
  }
88
107
  }
@@ -168,10 +168,10 @@ class PlanRunService {
168
168
  return spec
169
169
  }
170
170
 
171
- async listPlanSpecsByWorkstream(workstreamId: RecordIdInput): Promise<PlanSpecRecord[]> {
171
+ async listPlanSpecsByThread(threadId: RecordIdInput): Promise<PlanSpecRecord[]> {
172
172
  return databaseService.findMany(
173
173
  TABLES.PLAN_SPEC,
174
- { workstreamId: ensureRecordId(workstreamId, TABLES.WORKSTREAM) },
174
+ { threadId: ensureRecordId(threadId, TABLES.THREAD) },
175
175
  PlanSpecSchema,
176
176
  { orderBy: 'createdAt', orderDir: 'DESC' },
177
177
  )
@@ -219,15 +219,15 @@ class PlanRunService {
219
219
  return run
220
220
  }
221
221
 
222
- async getActiveRunRecord(workstreamId: RecordIdInput): Promise<PlanRunRecord | null> {
223
- const runs = await this.getActiveRunRecords(workstreamId)
222
+ async getActiveRunRecord(threadId: RecordIdInput): Promise<PlanRunRecord | null> {
223
+ const runs = await this.getActiveRunRecords(threadId)
224
224
  return runs[0] ?? null
225
225
  }
226
226
 
227
- async getActiveRunRecords(workstreamId: RecordIdInput): Promise<PlanRunRecord[]> {
227
+ async getActiveRunRecords(threadId: RecordIdInput): Promise<PlanRunRecord[]> {
228
228
  const runs = await databaseService.findMany(
229
229
  TABLES.PLAN_RUN,
230
- { workstreamId: ensureRecordId(workstreamId, TABLES.WORKSTREAM) },
230
+ { threadId: ensureRecordId(threadId, TABLES.THREAD) },
231
231
  PlanRunSchema,
232
232
  { orderBy: 'updatedAt', orderDir: 'DESC' },
233
233
  )
@@ -379,7 +379,6 @@ class PlanRunService {
379
379
  status: nodeRun.status,
380
380
  upstreamNodeIds: [...nodeSpec.upstreamNodeIds],
381
381
  downstreamNodeIds: [...nodeSpec.downstreamNodeIds],
382
- ...(nodeRun.handoffContext ? { handoffContext: nodeRun.handoffContext } : {}),
383
382
  ...(nodeRun.completedAt ? { completedAt: toOptionalIsoDateTimeString(nodeRun.completedAt) } : {}),
384
383
  } as SerializablePlanNode
385
384
  }
@@ -418,7 +417,6 @@ class PlanRunService {
418
417
  resolvedInput: nodeRun.resolvedInput,
419
418
  latestStructuredOutput: nodeRun.latestStructuredOutput,
420
419
  latestNotes: nodeRun.latestNotes,
421
- handoffContext: nodeRun.handoffContext,
422
420
  blockedReason: nodeRun.blockedReason,
423
421
  failureClass: nodeRun.failureClass,
424
422
  upstreamNodeIds: [...nodeSpec.upstreamNodeIds],
@@ -432,7 +430,7 @@ class PlanRunService {
432
430
  return {
433
431
  specId: recordIdToString(spec.id, TABLES.PLAN_SPEC),
434
432
  runId: recordIdToString(run.id, TABLES.PLAN_RUN),
435
- workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
433
+ threadId: recordIdToString(run.threadId, TABLES.THREAD),
436
434
  organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
437
435
  title: spec.title,
438
436
  objective: spec.objective,
@@ -41,7 +41,7 @@ class PlanSchedulerService {
41
41
 
42
42
  async createSchedule(params: {
43
43
  organizationId: RecordIdInput
44
- workstreamId: RecordIdInput
44
+ threadId: RecordIdInput
45
45
  planSpecId?: RecordIdInput
46
46
  runId?: RecordIdInput
47
47
  nodeId?: string
@@ -54,7 +54,7 @@ class PlanSchedulerService {
54
54
  TABLES.PLAN_SCHEDULE,
55
55
  {
56
56
  organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
57
- workstreamId: ensureRecordId(params.workstreamId, TABLES.WORKSTREAM),
57
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
58
58
  planSpecId: params.planSpecId ? ensureRecordId(params.planSpecId, TABLES.PLAN_SPEC) : undefined,
59
59
  runId: params.runId ? ensureRecordId(params.runId, TABLES.PLAN_RUN) : undefined,
60
60
  nodeId: params.nodeId,
@@ -227,10 +227,10 @@ class PlanSchedulerService {
227
227
  }
228
228
  }
229
229
 
230
- async listSchedules(workstreamId: RecordIdInput): Promise<PlanScheduleRecord[]> {
230
+ async listSchedules(threadId: RecordIdInput): Promise<PlanScheduleRecord[]> {
231
231
  return databaseService.findMany(
232
232
  TABLES.PLAN_SCHEDULE,
233
- { workstreamId: ensureRecordId(workstreamId, TABLES.WORKSTREAM) },
233
+ { threadId: ensureRecordId(threadId, TABLES.THREAD) },
234
234
  PlanScheduleRecordSchema,
235
235
  { orderBy: 'createdAt', orderDir: 'ASC' },
236
236
  )
@@ -87,7 +87,7 @@ class PlanTemplateService {
87
87
  async instantiate(params: {
88
88
  templateId: RecordIdInput
89
89
  organizationId: RecordIdInput
90
- workstreamId: RecordIdInput
90
+ threadId: RecordIdInput
91
91
  leadAgentId: string
92
92
  overrides?: Partial<PlanDraft>
93
93
  carryForwardArtifacts?: PlanArtifactRecord[]
@@ -106,7 +106,7 @@ class PlanTemplateService {
106
106
 
107
107
  return executionPlanService.createPlan({
108
108
  organizationId: params.organizationId,
109
- workstreamId: params.workstreamId,
109
+ threadId: params.threadId,
110
110
  leadAgentId: params.leadAgentId,
111
111
  input: draft,
112
112
  })
@@ -582,16 +582,6 @@ class PlanValidatorService {
582
582
  }
583
583
 
584
584
  if (deliverable.schemaRef) {
585
- if (artifact.schemaRef !== deliverable.schemaRef) {
586
- blocking.push(
587
- createIssue({
588
- code: 'artifact_schema_mismatch',
589
- message: `Artifact "${deliverable.name}" must declare schemaRef "${deliverable.schemaRef}".`,
590
- nodeId: params.node.id,
591
- }),
592
- )
593
- }
594
-
595
585
  const artifactSchema = resolveSchemaRef(params.draft, deliverable.schemaRef)
596
586
  if (!artifact.payload) {
597
587
  blocking.push(
@@ -658,7 +648,6 @@ class PlanValidatorService {
658
648
  if (check.type === 'schema') {
659
649
  const schemaRef =
660
650
  (typeof check.config.schemaRef === 'string' ? check.config.schemaRef : undefined) ??
661
- artifact?.schemaRef ??
662
651
  (artifactName ? node.deliverables.find((candidate) => candidate.name === artifactName)?.schemaRef : undefined)
663
652
  if (!schemaRef) {
664
653
  return createIssue({
@@ -20,7 +20,7 @@ function buildPluginExecutionParams(params: {
20
20
  inputs: params.resolvedInput,
21
21
  context: {
22
22
  organizationId: params.context.organizationId,
23
- workstreamId: params.context.workstreamId,
23
+ threadId: params.context.threadId,
24
24
  planId: params.context.planId,
25
25
  nodeId: params.context.nodeId,
26
26
  ...(params.context.userId ? { userId: params.context.userId } : {}),
@@ -141,7 +141,7 @@ function extractJobContext(data: unknown): Record<string, unknown> | undefined {
141
141
 
142
142
  const context = compactRecord({
143
143
  organizationId: readStringField(record, 'organizationId') ?? readStringField(record, 'orgId'),
144
- workstreamId: readStringField(record, 'workstreamId'),
144
+ threadId: readStringField(record, 'threadId'),
145
145
  userId: readStringField(record, 'userId'),
146
146
  agentId: readStringField(record, 'agentId'),
147
147
  sourceId: readStringField(record, 'sourceId'),
@@ -18,7 +18,7 @@ function buildRefinementPromptInput(
18
18
  `sourceLabel=${candidate.sourceLabel}`,
19
19
  `systemTitle=${candidate.systemTitle}`,
20
20
  metadata.agentName ? `agentName=${metadata.agentName}` : null,
21
- metadata.workstreamTitle ? `workstreamTitle=${metadata.workstreamTitle}` : null,
21
+ metadata.threadTitle ? `threadTitle=${metadata.threadTitle}` : null,
22
22
  metadata.userMessageText ? `userMessage=${metadata.userMessageText}` : null,
23
23
  metadata.assistantSummary ? `assistantSummary=${metadata.assistantSummary}` : null,
24
24
  ].filter((line): line is string => Boolean(line))
@@ -79,9 +79,9 @@ function shouldKeepExistingAgentTitle(existing: RecentActivityRow | null): boole
79
79
  function buildRecentActivityAreaKey(
80
80
  row: Pick<RecentActivityRow, 'targetKind' | 'targetId' | 'kind' | 'mergeKey' | 'metadata'>,
81
81
  ): string {
82
- const workstreamId = row.metadata?.workstreamId
83
- if (workstreamId) {
84
- return `workstream:${compactWhitespace(workstreamId)}`
82
+ const threadId = row.metadata?.threadId
83
+ if (threadId) {
84
+ return `thread:${compactWhitespace(threadId)}`
85
85
  }
86
86
 
87
87
  if (row.targetId) {
@@ -373,7 +373,7 @@ class RecentActivityService {
373
373
  'chat',
374
374
  'agent task',
375
375
  'recent activity',
376
- 'workstream update',
376
+ 'thread update',
377
377
  ])
378
378
 
379
379
  return !bannedTitles.has(normalizedCandidate)
@@ -8,7 +8,7 @@ const BUILT_IN_SYSTEM_EXECUTORS = Object.freeze({
8
8
  'plan-runtime': {
9
9
  supportedOperations: ['echo-input'],
10
10
  async executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult> {
11
- return { structuredOutput: structuredClone(params.inputs), artifacts: [] }
11
+ return { notes: 'System echo-input completed.', structuredOutput: structuredClone(params.inputs), artifacts: [] }
12
12
  },
13
13
  } satisfies SystemNodeExecutor,
14
14
  })
@@ -36,7 +36,7 @@ function buildSystemExecutionParams(params: {
36
36
  inputs: params.resolvedInput,
37
37
  context: {
38
38
  organizationId: params.context.organizationId,
39
- workstreamId: params.context.workstreamId,
39
+ threadId: params.context.threadId,
40
40
  planId: params.context.planId,
41
41
  nodeId: params.context.nodeId,
42
42
  ...(params.context.userId ? { userId: params.context.userId } : {}),