@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
@@ -1,5 +1,5 @@
1
1
  import {
2
- WORKSTREAM,
2
+ THREAD,
3
3
  dataPartsSchemas,
4
4
  messageMetadataSchema,
5
5
  PlanNodeResultSubmissionSchema,
@@ -11,13 +11,13 @@ import type { ChatMessage, MessageMetadata, PlanNodeSpecRecord } from '@lota-sdk
11
11
  import { convertToModelMessages, stepCountIs, tool as createTool, validateUIMessages } from 'ai'
12
12
  import type { PrepareStepFunction, StopCondition, ToolLoopAgent, ToolSet, UIMessageStreamWriter } from 'ai'
13
13
 
14
- import type { CoreWorkstreamProfile } from '../config/agent-defaults'
14
+ import type { CoreThreadProfile } from '../config/agent-defaults'
15
15
  import {
16
16
  agentRoster,
17
17
  buildAgentTools,
18
18
  createAgent,
19
19
  getLeadAgentId,
20
- getCoreWorkstreamProfile,
20
+ getCoreThreadProfile,
21
21
  getAgentRuntimeConfig,
22
22
  } from '../config/agent-defaults'
23
23
  import { lotaDebugLogger } from '../config/debug-logger'
@@ -26,7 +26,7 @@ import type { RecordIdRef } from '../db/record-id'
26
26
  import { recordIdToString } from '../db/record-id'
27
27
  import { TABLES } from '../db/tables'
28
28
  import { enqueueContextCompaction } from '../queues/context-compaction.queue'
29
- import { enqueueWorkstreamTitleGeneration } from '../queues/title-generation.queue'
29
+ import { enqueueThreadTitleGeneration } from '../queues/title-generation.queue'
30
30
  import { readRuntimeAgentIdentityOverrides, resolveRuntimeAgentDisplayName } from '../runtime/agent-identity-overrides'
31
31
  import { OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES } from '../runtime/agent-runtime-policy'
32
32
  import { createAgentMessageMetadata, createServerRunAbortController } from '../runtime/agent-stream-helpers'
@@ -39,7 +39,6 @@ import { createExecutionPlanInstructionSectionCache } from '../runtime/execution
39
39
  import { mergeInstructionSections } from '../runtime/instruction-sections'
40
40
  import { runPostTurnSideEffects } from '../runtime/post-turn-side-effects'
41
41
  import { getRuntimeAdapters, getToolProviders, getTurnHooks } from '../runtime/runtime-extensions'
42
- import { finalizeTurnRun } from '../runtime/turn-lifecycle'
43
42
  import {
44
43
  asRecord,
45
44
  collectToolOutputErrors,
@@ -47,17 +46,18 @@ import {
47
46
  readInstructionSections,
48
47
  readOptionalString,
49
48
  toOptionalTrimmedString,
50
- } from '../runtime/workstream-chat-helpers'
49
+ } from '../runtime/thread-chat-helpers'
51
50
  import {
52
51
  buildPlanTurnInstructionSections,
53
52
  buildPlanTurnPromptMessage,
54
53
  buildPlanTurnSubmitToolDescription,
55
- } from '../runtime/workstream-plan-turn'
56
- import type { WorkstreamPlanTurnContext } from '../runtime/workstream-plan-turn'
57
- import { assembleWorkstreamTurnContext } from '../runtime/workstream-turn-context'
54
+ } from '../runtime/thread-plan-turn'
55
+ import type { ThreadPlanTurnContext } from '../runtime/thread-plan-turn'
56
+ import { assembleThreadTurnContext } from '../runtime/thread-turn-context'
57
+ import { finalizeTurnRun } from '../runtime/turn-lifecycle'
58
58
  import { chatRunRegistry } from '../services/chat-run-registry.service'
59
- import type { NormalizedWorkstream, WorkstreamRecord } from '../services/workstream.types'
60
- import { triageWorkstreamMessage, checkForNextAgent } from '../system-agents/workstream-router.agent'
59
+ import type { NormalizedThread, ThreadRecord } from '../services/thread.types'
60
+ import { triageThreadMessage, checkForNextAgent } from '../system-agents/thread-router.agent'
61
61
  import { safeEnqueue } from '../utils/async'
62
62
  import { AppError } from '../utils/errors'
63
63
  import { attachmentService } from './attachment.service'
@@ -67,8 +67,8 @@ import { executionPlanService } from './execution-plan.service'
67
67
  import { learnedSkillService } from './learned-skill.service'
68
68
  import { memoryService } from './memory.service'
69
69
  import { planRunService } from './plan-run.service'
70
- import { workstreamMessageService } from './workstream-message.service'
71
- import { ActiveWorkstreamRunConflictError, workstreamService } from './workstream.service'
70
+ import { threadMessageService } from './thread-message.service'
71
+ import { ActiveThreadRunConflictError, threadService } from './thread.service'
72
72
 
73
73
  type ChatStreamChunk = Parameters<UIMessageStreamWriter<ChatMessage>['write']>[0]
74
74
 
@@ -86,22 +86,22 @@ function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
86
86
 
87
87
  const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
88
88
 
89
- async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
89
+ async function waitForThreadCompactionIfNeeded(threadId: RecordIdRef): Promise<ThreadRecord> {
90
90
  return waitForCompactionIfNeeded({
91
- entityId: recordIdToString(workstreamId, TABLES.WORKSTREAM),
92
- entityLabel: 'Workstream',
93
- loadEntity: () => workstreamService.getById(workstreamId),
94
- isCompacting: (workstream) => workstream.isCompacting === true,
91
+ entityId: recordIdToString(threadId, TABLES.THREAD),
92
+ entityLabel: 'Thread',
93
+ loadEntity: () => threadService.getById(threadId),
94
+ isCompacting: (thread) => thread.isCompacting === true,
95
95
  })
96
96
  }
97
97
 
98
- class WorkstreamTurnError extends AppError {
98
+ class ThreadTurnError extends AppError {
99
99
  constructor(
100
100
  message: string,
101
101
  readonly statusCode: 400 | 409,
102
102
  ) {
103
103
  super(message, statusCode === 409 ? 'CONFLICT' : 'BAD_REQUEST', statusCode)
104
- this.name = 'WorkstreamTurnError'
104
+ this.name = 'ThreadTurnError'
105
105
  }
106
106
  }
107
107
 
@@ -143,9 +143,9 @@ function applyPlanTurnToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpecRecord):
143
143
  )
144
144
  }
145
145
 
146
- export interface WorkstreamTurnParams {
147
- workstream: NormalizedWorkstream
148
- workstreamRef: RecordIdRef
146
+ export interface ThreadTurnParams {
147
+ thread: NormalizedThread
148
+ threadRef: RecordIdRef
149
149
  orgRef: RecordIdRef
150
150
  userRef: RecordIdRef
151
151
  userName?: string | null
@@ -156,9 +156,9 @@ export interface WorkstreamTurnParams {
156
156
  streamId?: string
157
157
  }
158
158
 
159
- export interface WorkstreamApprovalContinuationParams {
160
- workstream: NormalizedWorkstream
161
- workstreamRef: RecordIdRef
159
+ export interface ThreadApprovalContinuationParams {
160
+ thread: NormalizedThread
161
+ threadRef: RecordIdRef
162
162
  orgRef: RecordIdRef
163
163
  userRef: RecordIdRef
164
164
  userName?: string | null
@@ -167,20 +167,20 @@ export interface WorkstreamApprovalContinuationParams {
167
167
  streamId?: string
168
168
  }
169
169
 
170
- export interface WorkstreamPlanTurnParams {
171
- workstream: NormalizedWorkstream
172
- workstreamRef: RecordIdRef
170
+ export interface ThreadPlanTurnParams {
171
+ thread: NormalizedThread
172
+ threadRef: RecordIdRef
173
173
  orgRef: RecordIdRef
174
174
  userRef: RecordIdRef
175
175
  userName?: string | null
176
- planTurn: WorkstreamPlanTurnContext
176
+ planTurn: ThreadPlanTurnContext
177
177
  abortSignal?: AbortSignal
178
178
  streamId?: string
179
179
  }
180
180
 
181
- type WorkstreamRunCoreParams = {
182
- workstream: NormalizedWorkstream
183
- workstreamRef: RecordIdRef
181
+ type ThreadRunCoreParams = {
182
+ thread: NormalizedThread
183
+ threadRef: RecordIdRef
184
184
  orgRef: RecordIdRef
185
185
  userRef: RecordIdRef
186
186
  userName?: string | null
@@ -191,15 +191,15 @@ type WorkstreamRunCoreParams = {
191
191
  | { kind: 'userTurn'; inputMessage: ChatMessage; skipInputMessagePersistence?: boolean }
192
192
  | { kind: 'approvalContinuation'; approvalMessages: ChatMessage[] }
193
193
  | { kind: 'nativeToolApprovalTurn'; approvalMessages: ChatMessage[] }
194
- | { kind: 'planTurn'; planTurn: WorkstreamPlanTurnContext }
194
+ | { kind: 'planTurn'; planTurn: ThreadPlanTurnContext }
195
195
  )
196
196
 
197
- interface PreparedWorkstreamTurn {
197
+ interface PreparedThreadTurn {
198
198
  originalMessages: ChatMessage[]
199
- run: (writer?: UIMessageStreamWriter<ChatMessage>) => Promise<PreparedWorkstreamTurnResult>
199
+ run: (writer?: UIMessageStreamWriter<ChatMessage>) => Promise<PreparedThreadTurnResult>
200
200
  }
201
201
 
202
- export interface PreparedWorkstreamTurnResult {
202
+ export interface PreparedThreadTurnResult {
203
203
  inputMessageId?: string
204
204
  assistantMessages: ChatMessage[]
205
205
  }
@@ -217,8 +217,8 @@ function upsertChatHistoryMessage(messages: ChatMessage[], nextMessage: ChatMess
217
217
 
218
218
  interface StreamAgentResponseContext {
219
219
  turnHooks: ReturnType<typeof getTurnHooks>
220
- workstream: NormalizedWorkstream
221
- workstreamRef: RecordIdRef
220
+ thread: NormalizedThread
221
+ threadRef: RecordIdRef
222
222
  orgRef: RecordIdRef
223
223
  userRef: RecordIdRef
224
224
  userName?: string | null
@@ -238,7 +238,7 @@ interface StreamAgentResponseContext {
238
238
 
239
239
  interface StreamAgentResponseParams {
240
240
  agentId: string
241
- mode: 'direct' | 'fixedWorkstreamMode' | 'workstreamMode'
241
+ mode: 'direct' | 'fixedThreadMode' | 'threadMode'
242
242
  messages: ChatMessage[]
243
243
  tools: ToolSet
244
244
  observer: {
@@ -269,8 +269,8 @@ async function streamAgentResponse(
269
269
  await ctx.turnHooks.resolveAgent?.({
270
270
  agentId: streamParams.agentId,
271
271
  mode: streamParams.mode,
272
- workstream: ctx.workstream,
273
- workstreamRef: ctx.workstreamRef,
272
+ thread: ctx.thread,
273
+ threadRef: ctx.threadRef,
274
274
  orgRef: ctx.orgRef,
275
275
  userRef: ctx.userRef,
276
276
  userName: ctx.userName,
@@ -306,7 +306,7 @@ async function streamAgentResponse(
306
306
  resolvedAgentId === 'mentor'
307
307
  const config = getAgentRuntimeConfig({
308
308
  agentId: resolvedAgentId,
309
- workstreamMode: ctx.workstream.mode,
309
+ threadType: ctx.thread.type,
310
310
  mode: streamParams.mode,
311
311
  skills: streamParams.skills,
312
312
  onboardingActive: ctx.onboardingActive,
@@ -314,7 +314,7 @@ async function streamAgentResponse(
314
314
  systemWorkspaceDetails: ctx.promptContext.systemWorkspaceDetails,
315
315
  preSeededMemoriesSection,
316
316
  retrievedKnowledgeSection: ctx.retrievedKnowledgeSection,
317
- workstreamMemoryBlock: ctx.memoryBlock,
317
+ threadMemoryBlock: ctx.memoryBlock,
318
318
  learnedSkillsSection,
319
319
  userMessageText: latestUserMessageText,
320
320
  ruleOptions: { includeMemr3Rule: hasRetrievalTools, includeDomainReasoningFallbackRule: hasDomainRoutingSkills },
@@ -426,15 +426,15 @@ async function streamAgentResponse(
426
426
  return responseMessage
427
427
  }
428
428
 
429
- export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams): Promise<PreparedWorkstreamTurn> {
430
- const { workstream, workstreamRef, orgRef, userRef, userName } = params
429
+ export async function prepareThreadRunCore(params: ThreadRunCoreParams): Promise<PreparedThreadTurn> {
430
+ const { thread, threadRef, orgRef, userRef, userName } = params
431
431
  const runtimeAdapters = getRuntimeAdapters()
432
432
  const turnHooks = getTurnHooks()
433
433
  const toolProviders = getToolProviders()
434
434
  const workspaceProvider = runtimeAdapters.workspaceProvider
435
435
  const orgIdString = recordIdToString(orgRef, TABLES.ORGANIZATION)
436
436
  const userIdString = recordIdToString(userRef, TABLES.USER)
437
- const workstreamIdString = recordIdToString(workstreamRef, TABLES.WORKSTREAM)
437
+ const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
438
438
 
439
439
  const hydrateMessageFileUrls = (message: ChatMessage): ChatMessage => ({
440
440
  ...message,
@@ -453,13 +453,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
453
453
  if (params.kind === 'userTurn') {
454
454
  inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage, Date.now()))
455
455
  if (inputMessage.role !== 'user') {
456
- throw new WorkstreamTurnError('Only user messages can be submitted to the workstream runtime.', 400)
456
+ throw new ThreadTurnError('Only user messages can be submitted to the thread runtime.', 400)
457
457
  }
458
458
  if (!hasMessageContent(inputMessage.parts)) {
459
- throw new WorkstreamTurnError('Workstream messages must include text or attachments.', 400)
459
+ throw new ThreadTurnError('Thread messages must include text or attachments.', 400)
460
460
  }
461
- if (workstream.mode === 'direct' && !workstream.agentId) {
462
- throw new WorkstreamTurnError('Direct workstreams require an assigned agent.', 400)
461
+ if (thread.type === 'default' && !thread.agentId) {
462
+ throw new ThreadTurnError('Default threads require an assigned agent.', 400)
463
463
  }
464
464
  }
465
465
 
@@ -471,17 +471,14 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
471
471
  ? workspaceProvider.getWorkspace(orgRef)
472
472
  : Promise.resolve({})
473
473
 
474
- const workstreamRecord = await waitForWorkstreamCompactionIfNeeded(workstreamRef)
474
+ const threadRecord = await waitForThreadCompactionIfNeeded(threadRef)
475
475
  timer.step('compaction-gate')
476
476
  // Plan turns run without the chat lease — they must not block or be blocked by user messages.
477
477
  if (params.kind !== 'planTurn') {
478
- if (
479
- (await workstreamService.hasActiveRunLease(workstreamRef)) ||
480
- toOptionalTrimmedString(workstreamRecord.activeRunId)
481
- ) {
482
- const clearedStaleRun = await workstreamService.clearStaleActiveRunIfMissingFromRegistry(workstreamRef)
478
+ if ((await threadService.hasActiveRunLease(threadRef)) || toOptionalTrimmedString(threadRecord.activeRunId)) {
479
+ const clearedStaleRun = await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
483
480
  if (!clearedStaleRun) {
484
- throw new WorkstreamTurnError('A chat run is already active.', 409)
481
+ throw new ThreadTurnError('A chat run is already active.', 409)
485
482
  }
486
483
  }
487
484
  }
@@ -491,22 +488,19 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
491
488
  .reverse()
492
489
  .find((m) => m.role === 'assistant' && hasApprovalRespondedParts(m))
493
490
  if (!approvedAssistantMessage) {
494
- throw new WorkstreamTurnError('No approval-responded message found.', 400)
491
+ throw new ThreadTurnError('No approval-responded message found.', 400)
495
492
  }
496
- await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [approvedAssistantMessage] })
493
+ await threadMessageService.upsertMessages({ threadId: threadRef, messages: [approvedAssistantMessage] })
497
494
  timer.step('persist-approval-message')
498
495
  }
499
496
 
500
- const persistedCompactionCursor = toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined
501
- const persistedLiveHistoryPromise = workstreamMessageService.listMessagesAfterCursor(
502
- workstreamRef,
503
- persistedCompactionCursor,
504
- )
497
+ const persistedCompactionCursor = toOptionalTrimmedString(threadRecord.lastCompactedMessageId) ?? undefined
498
+ const persistedLiveHistoryPromise = threadMessageService.listMessagesAfterCursor(threadRef, persistedCompactionCursor)
505
499
  let recentHistoryPromise: Promise<ChatMessage[]> | null = null
506
500
  const loadRecentHistory = async (): Promise<ChatMessage[]> => {
507
501
  if (!recentHistoryPromise) {
508
- recentHistoryPromise = workstreamMessageService
509
- .listRecentMessages(workstreamRef, 64)
502
+ recentHistoryPromise = threadMessageService
503
+ .listRecentMessages(threadRef, 64)
510
504
  .then(async (persistedRecentHistory) => {
511
505
  if (persistedRecentHistory.length === 0) {
512
506
  return [] as ChatMessage[]
@@ -547,7 +541,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
547
541
  timer.step('validate+hydrate-history')
548
542
 
549
543
  if (userMessage && shouldPersistInputMessage) {
550
- await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [userMessage] })
544
+ await threadMessageService.upsertMessages({ threadId: threadRef, messages: [userMessage] })
551
545
  timer.step('persist-user-message')
552
546
  }
553
547
 
@@ -569,7 +563,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
569
563
  const respondedBy = recordIdToString(userRef, TABLES.USER)
570
564
  if (params.kind === 'approvalContinuation') {
571
565
  await executionPlanService.applyApprovalResponseFromMessages({
572
- workstreamId: workstreamRef,
566
+ threadId: threadRef,
573
567
  approvalMessages: params.approvalMessages,
574
568
  respondedBy,
575
569
  })
@@ -580,31 +574,29 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
580
574
 
581
575
  if (
582
576
  params.kind === 'userTurn' &&
583
- workstream.mode === 'group' &&
584
- !workstream.core &&
585
- workstreamRecord.nameGenerated !== true &&
586
- workstreamRecord.title === WORKSTREAM.DEFAULT_TITLE &&
577
+ thread.type === 'group' &&
578
+ threadRecord.nameGenerated !== true &&
579
+ threadRecord.title === THREAD.DEFAULT_TITLE &&
587
580
  messageText.length > 0
588
581
  ) {
589
- void safeEnqueue(
590
- () => enqueueWorkstreamTitleGeneration({ workstreamId: workstreamIdString, sourceText: messageText }),
591
- { operationName: 'workstream-title-generation' },
592
- )
582
+ void safeEnqueue(() => enqueueThreadTitleGeneration({ threadId: threadIdString, sourceText: messageText }), {
583
+ operationName: 'thread-title-generation',
584
+ })
593
585
  }
594
586
 
595
- if (workstream.core && !workstream.coreType) {
596
- throw new WorkstreamTurnError('Core workstreams require a core type.', 400)
587
+ if (thread.type === 'thread' && !thread.threadType) {
588
+ throw new ThreadTurnError('Core threads require a thread type.', 400)
597
589
  }
598
- const coreWorkstreamProfile: CoreWorkstreamProfile | null =
599
- workstream.core && workstream.coreType ? getCoreWorkstreamProfile(workstream.coreType) : null
590
+ const coreThreadProfile: CoreThreadProfile | null =
591
+ thread.type === 'thread' && thread.threadType ? getCoreThreadProfile(thread.threadType) : null
600
592
  const defaultLeadAgentId = getLeadAgentId()
601
- const visibleWorkstreamAgentId =
593
+ const visibleThreadAgentId =
602
594
  params.agentIdOverride ??
603
- (workstream.mode === 'direct' ? workstream.agentId : (coreWorkstreamProfile?.config.agentId ?? defaultLeadAgentId))
604
- const coreInstructionSections = coreWorkstreamProfile ? [coreWorkstreamProfile.instructions] : undefined
605
- const assembledContext = await assembleWorkstreamTurnContext({
606
- workstream,
607
- workstreamRef,
595
+ (thread.type === 'default' ? thread.agentId : (coreThreadProfile?.config.agentId ?? defaultLeadAgentId))
596
+ const coreInstructionSections = coreThreadProfile ? [coreThreadProfile.instructions] : undefined
597
+ const assembledContext = await assembleThreadTurnContext({
598
+ thread,
599
+ threadRef,
608
600
  orgRef,
609
601
  userRef,
610
602
  userName,
@@ -628,11 +620,11 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
628
620
  hookInstructionSections,
629
621
  } = assembledContext
630
622
 
631
- let memoryBlock = workstreamService.formatMemoryBlockForPrompt(workstreamRecord)
623
+ let memoryBlock = threadService.formatMemoryBlockForPrompt(threadRecord)
632
624
  const executionPlanInstructionSectionCache = createExecutionPlanInstructionSectionCache({
633
625
  disabled: false,
634
626
  loadPlans: async () => {
635
- const runs = await planRunService.getActiveRunRecords(workstreamRef)
627
+ const runs = await planRunService.getActiveRunRecords(threadRef)
636
628
  return Promise.all(runs.map((run) => planRunService.toSerializablePlan(run, { slim: true })))
637
629
  },
638
630
  })
@@ -643,7 +635,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
643
635
  }
644
636
  if (userMessage) {
645
637
  const appliedHumanInput = await executionPlanService.applyHumanInputFromUserMessage({
646
- workstreamId: workstreamRef,
638
+ threadId: threadRef,
647
639
  message: userMessage,
648
640
  respondedBy,
649
641
  })
@@ -684,8 +676,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
684
676
  }
685
677
 
686
678
  const persistedCompactionSummary =
687
- persistedCompactionCursor && typeof workstreamRecord.compactionSummary === 'string'
688
- ? workstreamRecord.compactionSummary
679
+ persistedCompactionCursor && typeof threadRecord.compactionSummary === 'string'
680
+ ? threadRecord.compactionSummary
689
681
  : ''
690
682
  const messagesForContext = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
691
683
  let currentMessages = contextCompactionRuntime.prependSummaryMessage(persistedCompactionSummary, messagesForContext)
@@ -704,7 +696,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
704
696
  })
705
697
  const buildTurnToolParams = (toolParams: {
706
698
  agentId: string
707
- mode: 'direct' | 'fixedWorkstreamMode' | 'workstreamMode'
699
+ mode: 'direct' | 'fixedThreadMode' | 'threadMode'
708
700
  memoryBlock: string
709
701
  onAppendMemoryBlock: (value: string) => void
710
702
  extraMessages?: ChatMessage[]
@@ -715,9 +707,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
715
707
  orgId: orgRef,
716
708
  userId: userRef,
717
709
  userName: userName ?? 'there',
718
- workstreamId: workstreamRef,
710
+ threadId: threadRef,
719
711
  orgIdString,
720
- workstreamMode: workstream.mode,
712
+ threadType: thread.type,
721
713
  mode: toolParams.mode,
722
714
  linearInstalled,
723
715
  onboardingActive,
@@ -738,7 +730,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
738
730
  return {
739
731
  originalMessages,
740
732
  run: async (writer?: UIMessageStreamWriter<ChatMessage>) => {
741
- const executeRun = async (leaseAbortSignal?: AbortSignal): Promise<PreparedWorkstreamTurnResult | void> => {
733
+ const executeRun = async (leaseAbortSignal?: AbortSignal): Promise<PreparedThreadTurnResult | void> => {
742
734
  const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(buildContextResult)
743
735
  const runTimer = lotaDebugLogger.timer('run')
744
736
  const serverRunId = Bun.randomUUIDv7()
@@ -751,7 +743,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
751
743
  }
752
744
  // Plan turns run without the chat lease — don't claim the active run slot.
753
745
  if (params.kind !== 'planTurn') {
754
- await workstreamService.setActiveTurn(workstreamRef, serverRunId, params.streamId ?? null)
746
+ await threadService.setActiveTurn(threadRef, serverRunId, params.streamId ?? null)
755
747
  chatRunRegistry.register(serverRunId, runAbort.controller)
756
748
  }
757
749
  runTimer.step('set-active-run+stream')
@@ -794,7 +786,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
794
786
  Date.now(),
795
787
  )
796
788
 
797
- await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [committed] })
789
+ await threadMessageService.upsertMessages({ threadId: threadRef, messages: [committed] })
798
790
  currentMessages = upsertChatHistoryMessage(currentMessages, committed)
799
791
  allAssistantMessages = upsertChatHistoryMessage(allAssistantMessages, committed)
800
792
  throwIfRunAborted()
@@ -804,8 +796,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
804
796
 
805
797
  const streamCtx: StreamAgentResponseContext = {
806
798
  turnHooks,
807
- workstream,
808
- workstreamRef,
799
+ thread,
800
+ threadRef,
809
801
  orgRef,
810
802
  userRef,
811
803
  userName,
@@ -825,7 +817,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
825
817
 
826
818
  const runVisibleAgent = async (runParams: {
827
819
  agentId: string
828
- mode: 'direct' | 'fixedWorkstreamMode' | 'workstreamMode'
820
+ mode: 'direct' | 'fixedThreadMode' | 'threadMode'
829
821
  skills?: string[]
830
822
  additionalInstructionSections?: string[]
831
823
  extraMessages?: ChatMessage[]
@@ -837,7 +829,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
837
829
  const visibleTimer = lotaDebugLogger.timer(`visible:${runParams.agentId}`)
838
830
  let runMemoryBlock = memoryBlock
839
831
  const includeExecutionPlanTools =
840
- runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedWorkstreamMode'
832
+ runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedThreadMode'
841
833
  const rawTools: ToolSet = {
842
834
  ...((await buildAgentTools(
843
835
  buildTurnToolParams({
@@ -889,7 +881,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
889
881
  inputSchema: PlanNodeResultSubmissionSchema,
890
882
  execute: async (result) =>
891
883
  await executionPlanService.submitPlanTurnResult({
892
- workstreamId: workstreamRef,
884
+ threadId: threadRef,
893
885
  runId: planTurn.runId,
894
886
  nodeId: planTurn.nodeId,
895
887
  emittedBy: planTurn.nodeSpec.owner.ref,
@@ -899,7 +891,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
899
891
 
900
892
  await runVisibleAgent({
901
893
  agentId: planTurn.nodeSpec.owner.ref,
902
- mode: workstream.mode === 'direct' ? 'direct' : 'workstreamMode',
894
+ mode: thread.type === 'default' ? 'direct' : 'threadMode',
903
895
  additionalInstructionSections: buildPlanTurnInstructionSections(planTurn),
904
896
  extraMessages: [buildPlanTurnPromptMessage(planTurn)],
905
897
  includeExecutionPlanTools: false,
@@ -908,16 +900,16 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
908
900
  metadataPatch: { trigger: 'plan-turn', planRunId: planTurn.runId, planNodeId: planTurn.nodeId },
909
901
  })
910
902
  } else {
911
- if (workstream.mode === 'direct') {
912
- if (!workstream.agentId) {
913
- throw new WorkstreamTurnError('Direct workstreams require an assigned agent.', 400)
903
+ if (thread.type === 'default') {
904
+ if (!thread.agentId) {
905
+ throw new ThreadTurnError('Direct threads require an assigned agent.', 400)
914
906
  }
915
- await runVisibleAgent({ agentId: workstream.agentId, mode: 'direct' })
907
+ await runVisibleAgent({ agentId: thread.agentId, mode: 'direct' })
916
908
  } else {
917
- // Multi-agent orchestration for group workstreams
918
- const wsMembers = (workstream as { members?: string[] }).members ?? []
909
+ // Multi-agent orchestration for group threads
910
+ const wsMembers = (thread as { members?: string[] }).members ?? []
919
911
  const members = wsMembers.length > 0 ? wsMembers : [...agentRoster]
920
- const fallbackAgentId = coreWorkstreamProfile?.config.agentId ?? defaultLeadAgentId
912
+ const fallbackAgentId = coreThreadProfile?.config.agentId ?? defaultLeadAgentId
921
913
  throwIfRunAborted()
922
914
  writeMultiAgentEvent(writer, { phase: 'routing', note: 'Routing this turn to the right agent.' })
923
915
 
@@ -926,8 +918,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
926
918
  .map((m) => `${m.role}: ${extractMessageText(m).slice(0, 200)}`)
927
919
  .join('\n')
928
920
 
929
- const triageResult = await triageWorkstreamMessage({
930
- workstreamTitle: workstream.title,
921
+ const triageResult = await triageThreadMessage({
922
+ threadTitle: thread.title,
931
923
  members,
932
924
  messageText,
933
925
  recentContext,
@@ -944,13 +936,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
944
936
  }
945
937
  // Multi-agent member protocol: be direct, focus on domain
946
938
  additionalSections.push(
947
- '<multi-agent-protocol>\nYou are responding as part of a multi-agent workstream. Focus on your domain expertise. Be direct and concise — another agent may follow up on different aspects.\n</multi-agent-protocol>',
939
+ '<multi-agent-protocol>\nYou are responding as part of a multi-agent thread. Focus on your domain expertise. Be direct and concise — another agent may follow up on different aspects.\n</multi-agent-protocol>',
948
940
  )
949
941
 
950
942
  return await runVisibleAgent({
951
943
  agentId,
952
- mode: 'workstreamMode',
953
- skills: coreWorkstreamProfile?.skills ? [...coreWorkstreamProfile.skills] : undefined,
944
+ mode: 'threadMode',
945
+ skills: coreThreadProfile?.skills ? [...coreThreadProfile.skills] : undefined,
954
946
  additionalInstructionSections: additionalSections,
955
947
  })
956
948
  }
@@ -973,7 +965,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
973
965
  while (respondedAgents.length < 3) {
974
966
  const lastResponseText = extractMessageText(lastResponse).slice(0, 500)
975
967
  const checkResult = await checkForNextAgent({
976
- workstreamTitle: workstream.title,
968
+ threadTitle: thread.title,
977
969
  members,
978
970
  messageText,
979
971
  respondedAgents,
@@ -1006,10 +998,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1006
998
  metadata: { hidden: true, createdAt: Date.now() } as MessageMetadata,
1007
999
  }
1008
1000
  throwIfRunAborted()
1009
- await workstreamMessageService.upsertMessages({
1010
- workstreamId: workstreamRef,
1011
- messages: [bridgeMessage],
1012
- })
1001
+ await threadMessageService.upsertMessages({ threadId: threadRef, messages: [bridgeMessage] })
1013
1002
  currentMessages = upsertChatHistoryMessage(currentMessages, bridgeMessage)
1014
1003
  throwIfRunAborted()
1015
1004
 
@@ -1033,13 +1022,12 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1033
1022
  }
1034
1023
  } finally {
1035
1024
  try {
1036
- const latestWorkstreamRecord = await workstreamService.getById(workstreamRef)
1025
+ const latestThreadRecord = await threadService.getById(threadRef)
1037
1026
 
1038
1027
  await finalizeTurnRun({
1039
1028
  serverRunId,
1040
- getEntity: async () => latestWorkstreamRecord,
1041
- getUncompactedMessages: (cursor) =>
1042
- workstreamMessageService.listMessagesAfterCursor(workstreamRef, cursor),
1029
+ getEntity: async () => latestThreadRecord,
1030
+ getUncompactedMessages: (cursor) => threadMessageService.listMessagesAfterCursor(threadRef, cursor),
1043
1031
  assessCompaction: (summaryText, messages) =>
1044
1032
  contextCompactionRuntime.shouldCompactHistory({
1045
1033
  summaryText,
@@ -1048,34 +1036,34 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1048
1036
  }),
1049
1037
  enqueueCompaction: async () => {
1050
1038
  await enqueueContextCompaction({
1051
- domain: 'workstream',
1052
- entityId: workstreamIdString,
1039
+ domain: 'thread',
1040
+ entityId: threadIdString,
1053
1041
  contextSize: CONTEXT_WINDOW_TOKENS,
1054
1042
  })
1055
1043
  },
1056
1044
  unregisterRun: (runId) => chatRunRegistry.unregister(runId),
1057
1045
  clearActiveRunId: async (runId) => {
1058
- const activeStreamId = await workstreamService.getActiveStreamId(workstreamRef)
1059
- await workstreamService.clearActiveTurn(workstreamRef, { runId, streamId: activeStreamId })
1046
+ const activeStreamId = await threadService.getActiveStreamId(threadRef)
1047
+ await threadService.clearActiveTurn(threadRef, { runId, streamId: activeStreamId })
1060
1048
  },
1061
1049
  disposeAbort: () => runAbort.dispose(),
1062
1050
  activeStreamId: params.streamId,
1063
1051
  clearActiveStreamId: async (streamId) => {
1064
- const activeRunId = await workstreamService.getActiveRunId(workstreamRef)
1052
+ const activeRunId = await threadService.getActiveRunId(threadRef)
1065
1053
  if (!activeRunId) return
1066
- await workstreamService.clearActiveTurn(workstreamRef, { runId: activeRunId, streamId })
1054
+ await threadService.clearActiveTurn(threadRef, { runId: activeRunId, streamId })
1067
1055
  },
1068
1056
  })
1069
1057
 
1070
1058
  if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
1071
1059
  await runPostTurnSideEffects({
1072
- workstream,
1073
- workstreamRef,
1060
+ thread,
1061
+ threadRef,
1074
1062
  orgRef,
1075
1063
  userRef,
1076
1064
  userName,
1077
1065
  orgIdString,
1078
- workstreamIdString,
1066
+ threadIdString,
1079
1067
  onboardingActive,
1080
1068
  workspace,
1081
1069
  allAssistantMessages,
@@ -1084,9 +1072,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1084
1072
  loadRecentHistory,
1085
1073
  listReadableUploads: () => listReadableUploads(),
1086
1074
  memoryBlock,
1087
- visibleWorkstreamAgentId,
1075
+ visibleThreadAgentId,
1088
1076
  defaultLeadAgentId,
1089
- latestWorkstreamRecord,
1077
+ latestThreadRecord,
1090
1078
  isUserTurn: params.kind === 'userTurn',
1091
1079
  agentDisplayNamesById: agentIdentityOverrides.displayNamesById,
1092
1080
  })
@@ -1094,20 +1082,20 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1094
1082
 
1095
1083
  if (allAssistantMessages.length > 0 && params.kind !== 'planTurn') {
1096
1084
  await turnHooks.afterTurn?.({
1097
- workstream,
1098
- workstreamRef,
1085
+ thread,
1086
+ threadRef,
1099
1087
  orgRef,
1100
1088
  userRef,
1101
1089
  userName,
1102
1090
  onboardingActive,
1103
1091
  referenceUserMessage,
1104
1092
  assistantMessages: allAssistantMessages,
1105
- latestWorkstreamRecord,
1093
+ latestThreadRecord,
1106
1094
  context: buildContextResult,
1107
1095
  })
1108
1096
  }
1109
1097
  } catch (postRunError) {
1110
- aiLogger.error`Workstream post-run cleanup failed: ${postRunError}`
1098
+ aiLogger.error`Thread post-run cleanup failed: ${postRunError}`
1111
1099
  }
1112
1100
  }
1113
1101
  }
@@ -1123,7 +1111,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1123
1111
  }
1124
1112
 
1125
1113
  try {
1126
- return await workstreamService.withActiveRunLease(workstreamRef, async (leaseAbortSignal) => {
1114
+ return await threadService.withActiveRunLease(threadRef, async (leaseAbortSignal) => {
1127
1115
  const runResult = await executeRun(leaseAbortSignal)
1128
1116
  if (runResult) {
1129
1117
  return runResult
@@ -1132,8 +1120,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1132
1120
  return { inputMessageId: referenceUserMessage?.id, assistantMessages: [...allAssistantMessages] }
1133
1121
  })
1134
1122
  } catch (error) {
1135
- if (error instanceof ActiveWorkstreamRunConflictError) {
1136
- throw new WorkstreamTurnError(error.message, 409)
1123
+ if (error instanceof ActiveThreadRunConflictError) {
1124
+ throw new ThreadTurnError(error.message, 409)
1137
1125
  }
1138
1126
  throw error
1139
1127
  }