@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
@@ -16,8 +16,8 @@ import type {
16
16
  import { agentRoster } from '../config/agent-defaults'
17
17
  import { serverLogger } from '../config/logger'
18
18
  import { executionPlanService } from './execution-plan.service'
19
- import { workstreamService } from './workstream.service'
20
- import type { NormalizedWorkstream } from './workstream.types'
19
+ import { threadService } from './thread.service'
20
+ import type { NormalizedThread } from './thread.types'
21
21
 
22
22
  const BOARD_COLUMN_ORDER = ['ready', 'running', 'awaiting-human', 'completed', 'blocked', 'failed'] as const
23
23
  type BoardColumnStatus = (typeof BOARD_COLUMN_ORDER)[number]
@@ -33,12 +33,12 @@ const COLUMN_LABELS: Record<BoardColumnStatus, string> = {
33
33
 
34
34
  type ActivePlanEntry = {
35
35
  plan: SerializableExecutionPlan
36
- workstream: Pick<NormalizedWorkstream, 'id' | 'title' | 'isRunning' | 'updatedAt'>
36
+ thread: Pick<NormalizedThread, 'id' | 'title' | 'isRunning' | 'updatedAt'>
37
37
  }
38
38
 
39
39
  type AgentActivityDeps = {
40
- executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForWorkstream'>
41
- workstreamService: Pick<typeof workstreamService, 'listWorkstreams'>
40
+ executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForThread'>
41
+ threadService: Pick<typeof threadService, 'listThreads'>
42
42
  }
43
43
 
44
44
  function normalizeCardStatus(status: string): BoardColumnStatus {
@@ -97,8 +97,8 @@ function incrementCounts(counts: AgentActivityCounts, rawStatus: string): void {
97
97
  export function planNodeToCard(
98
98
  node: SerializablePlanNode,
99
99
  plan: SerializableExecutionPlan,
100
- workstreamId: string,
101
- workstreamTitle: string,
100
+ threadId: string,
101
+ threadTitle: string,
102
102
  ): PlanNodeCard {
103
103
  const approval = plan.approvals.find((candidate) => candidate.nodeId === node.id && candidate.status === 'pending')
104
104
 
@@ -111,8 +111,8 @@ export function planNodeToCard(
111
111
  ownerRef: node.owner.ref,
112
112
  planRunId: plan.runId,
113
113
  planTitle: plan.title,
114
- workstreamId,
115
- workstreamTitle,
114
+ threadId,
115
+ threadTitle,
116
116
  nodeType: node.type,
117
117
  artifactCount: plan.artifacts.filter((artifact) => artifact.nodeId === node.id).length,
118
118
  hasApproval: Boolean(approval),
@@ -129,11 +129,11 @@ export function planNodeToCard(
129
129
  function buildPlanViewNode(
130
130
  node: SerializablePlanNode,
131
131
  plan: SerializableExecutionPlan,
132
- workstreamId: string,
133
- workstreamTitle: string,
132
+ threadId: string,
133
+ threadTitle: string,
134
134
  ): PlanViewNode {
135
135
  return {
136
- ...planNodeToCard(node, plan, workstreamId, workstreamTitle),
136
+ ...planNodeToCard(node, plan, threadId, threadTitle),
137
137
  deliverableNames: node.deliverables.map((deliverable) => deliverable.name),
138
138
  upstreamNodeIds: node.upstreamNodeIds,
139
139
  downstreamNodeIds: node.downstreamNodeIds,
@@ -141,12 +141,12 @@ function buildPlanViewNode(
141
141
  }
142
142
 
143
143
  export class AgentActivityService {
144
- constructor(private readonly deps: AgentActivityDeps = { executionPlanService, workstreamService }) {}
144
+ constructor(private readonly deps: AgentActivityDeps = { executionPlanService, threadService }) {}
145
145
 
146
146
  async getBoard(userRef: string, orgRef: string): Promise<PlanBoardResponse> {
147
147
  const activePlans = await this.getAllActivePlans(userRef, orgRef)
148
- const cards = activePlans.flatMap(({ plan, workstream }) =>
149
- plan.nodes.map((node) => planNodeToCard(node, plan, workstream.id, workstream.title)),
148
+ const cards = activePlans.flatMap(({ plan, thread }) =>
149
+ plan.nodes.map((node) => planNodeToCard(node, plan, thread.id, thread.title)),
150
150
  )
151
151
 
152
152
  const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
@@ -171,7 +171,7 @@ export class AgentActivityService {
171
171
  const match = activePlans.find(({ plan }) => plan.runId === planRunId)
172
172
  if (!match) return null
173
173
 
174
- const { plan, workstream } = match
174
+ const { plan, thread } = match
175
175
  return {
176
176
  planRunId: plan.runId,
177
177
  title: plan.title,
@@ -179,7 +179,7 @@ export class AgentActivityService {
179
179
  status: plan.status,
180
180
  leadAgentId: plan.leadAgentId,
181
181
  progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
182
- nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, workstream.id, workstream.title)),
182
+ nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, thread.id, thread.title)),
183
183
  edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
184
184
  }
185
185
  }
@@ -188,13 +188,13 @@ export class AgentActivityService {
188
188
  const activePlans = await this.getAllActivePlans(userRef, orgRef)
189
189
  const tasks: PlanNodeCard[] = []
190
190
 
191
- for (const { plan, workstream } of activePlans) {
191
+ for (const { plan, thread } of activePlans) {
192
192
  for (const node of plan.nodes) {
193
193
  const humanOwned = node.owner.executorType === 'user'
194
194
  const awaitingHuman = node.status === 'awaiting-human'
195
195
  if (!humanOwned && !awaitingHuman) continue
196
196
 
197
- tasks.push(planNodeToCard(node, plan, workstream.id, workstream.title))
197
+ tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
198
198
  }
199
199
  }
200
200
 
@@ -209,7 +209,7 @@ export class AgentActivityService {
209
209
  activityByAgent.set(agentId, this.createEmptyEntry(agentId))
210
210
  }
211
211
 
212
- for (const { plan, workstream } of activePlans) {
212
+ for (const { plan, thread } of activePlans) {
213
213
  const involvedAgents = new Set<string>()
214
214
 
215
215
  for (const node of plan.nodes) {
@@ -221,7 +221,7 @@ export class AgentActivityService {
221
221
  incrementCounts(entry.counts, node.status)
222
222
 
223
223
  if (!isCompletedStatus(node.status)) {
224
- entry.tasks.push(planNodeToCard(node, plan, workstream.id, workstream.title))
224
+ entry.tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
225
225
  }
226
226
  }
227
227
 
@@ -234,15 +234,15 @@ export class AgentActivityService {
234
234
  for (const agentId of involvedAgents) {
235
235
  const entry = this.ensureEntry(activityByAgent, agentId)
236
236
  this.ensureProjectEntry(entry.projects, {
237
- workstreamId: workstream.id,
238
- workstreamTitle: workstream.title,
237
+ threadId: thread.id,
238
+ threadTitle: thread.title,
239
239
  planRunId: plan.runId,
240
240
  planTitle: plan.title,
241
241
  status: plan.status,
242
242
  })
243
243
 
244
- entry.isRunning = entry.isRunning || workstream.isRunning
245
- entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, workstream.updatedAt)
244
+ entry.isRunning = entry.isRunning || thread.isRunning
245
+ entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
246
246
  }
247
247
  }
248
248
 
@@ -265,7 +265,7 @@ export class AgentActivityService {
265
265
  pendingApprovalCount: userTasks.pendingApprovalCount,
266
266
  awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
267
267
  lastActiveAt: activePlans.reduce<string | null>(
268
- (latest, entry) => maxIsoDate(latest, entry.workstream.updatedAt),
268
+ (latest, entry) => maxIsoDate(latest, entry.thread.updatedAt),
269
269
  null,
270
270
  ),
271
271
  },
@@ -274,14 +274,14 @@ export class AgentActivityService {
274
274
  }
275
275
 
276
276
  async getAllActivePlans(userRef: string, orgRef: string): Promise<ActivePlanEntry[]> {
277
- const workstreams = await this.listRelevantWorkstreams(userRef, orgRef)
277
+ const threads = await this.listRelevantThreads(userRef, orgRef)
278
278
  const planResults = await Promise.all(
279
- workstreams.map(async (workstream) => {
279
+ threads.map(async (thread) => {
280
280
  try {
281
- const plans = await this.deps.executionPlanService.getActivePlansForWorkstream(workstream.id)
282
- return plans.map((plan) => ({ plan, workstream }))
281
+ const plans = await this.deps.executionPlanService.getActivePlansForThread(thread.id)
282
+ return plans.map((plan) => ({ plan, thread }))
283
283
  } catch (error) {
284
- serverLogger.error`Failed to load active plans for workstream ${workstream.id}: ${error}`
284
+ serverLogger.error`Failed to load active plans for thread ${thread.id}: ${error}`
285
285
  return []
286
286
  }
287
287
  }),
@@ -290,26 +290,21 @@ export class AgentActivityService {
290
290
  return planResults.flat()
291
291
  }
292
292
 
293
- private async listRelevantWorkstreams(userRef: string, orgRef: string): Promise<NormalizedWorkstream[]> {
293
+ private async listRelevantThreads(userRef: string, orgRef: string): Promise<NormalizedThread[]> {
294
294
  const [direct, core, group] = await Promise.all([
295
- this.deps.workstreamService.listWorkstreams(userRef, orgRef, { mode: 'direct', includeArchived: false }),
296
- this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
297
- mode: 'group',
298
- core: true,
299
- includeArchived: false,
300
- }),
301
- this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
302
- mode: 'group',
303
- core: false,
295
+ this.deps.threadService.listThreads(userRef, orgRef, { type: 'default', includeArchived: false }),
296
+ this.deps.threadService.listThreads(userRef, orgRef, { type: 'thread', includeArchived: false }),
297
+ this.deps.threadService.listThreads(userRef, orgRef, {
298
+ type: 'group',
304
299
  includeArchived: false,
305
300
  take: 500,
306
301
  page: 1,
307
302
  }),
308
303
  ])
309
304
 
310
- const deduped = new Map<string, NormalizedWorkstream>()
311
- for (const workstream of [...direct.workstreams, ...core.workstreams, ...group.workstreams]) {
312
- deduped.set(workstream.id, workstream)
305
+ const deduped = new Map<string, NormalizedThread>()
306
+ for (const thread of [...direct.threads, ...core.threads, ...group.threads]) {
307
+ deduped.set(thread.id, thread)
313
308
  }
314
309
 
315
310
  return [...deduped.values()]
@@ -24,10 +24,10 @@ import {
24
24
  import { mergeInstructionSections } from '../runtime/instruction-sections'
25
25
  import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
26
26
  import { getTurnHooks } from '../runtime/runtime-extensions'
27
- import { asRecord, readInstructionSections, readOptionalString } from '../runtime/workstream-chat-helpers'
27
+ import { asRecord, readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
28
28
  import { nodeWorkspaceService } from './node-workspace.service'
29
29
  import type { PlanValidationIssueInput } from './plan-validator.service'
30
- import { WorkstreamSchema } from './workstream.types'
30
+ import { ThreadSchema } from './thread.types'
31
31
  import { writeIntentValidatorService } from './write-intent-validator.service'
32
32
 
33
33
  function applyToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpec): ToolSet {
@@ -108,20 +108,20 @@ class AgentExecutorService {
108
108
  throw new Error(`Agent executor "${agentId}" is not registered.`)
109
109
  }
110
110
 
111
- const workstream = await databaseService.findOne(
112
- TABLES.WORKSTREAM,
113
- { id: ensureRecordId(params.context.workstreamId, TABLES.WORKSTREAM) },
114
- WorkstreamSchema,
111
+ const thread = await databaseService.findOne(
112
+ TABLES.THREAD,
113
+ { id: ensureRecordId(params.context.threadId, TABLES.THREAD) },
114
+ ThreadSchema,
115
115
  )
116
- if (!workstream) {
117
- throw new Error(`Workstream ${params.context.workstreamId} not found for dispatched execution.`)
116
+ if (!thread) {
117
+ throw new Error(`Thread ${params.context.threadId} not found for dispatched execution.`)
118
118
  }
119
119
 
120
120
  const organizationRef = ensureRecordId(params.context.organizationId, TABLES.ORGANIZATION)
121
- const workstreamRef = ensureRecordId(params.context.workstreamId, TABLES.WORKSTREAM)
122
- const userRefSource = params.context.userId ?? workstream.userId
121
+ const threadRef = ensureRecordId(params.context.threadId, TABLES.THREAD)
122
+ const userRefSource = params.context.userId ?? thread.userId
123
123
  if (!userRefSource) {
124
- throw new Error(`Workstream ${params.context.workstreamId} is missing a user context for dispatched execution.`)
124
+ throw new Error(`Thread ${params.context.threadId} is missing a user context for dispatched execution.`)
125
125
  }
126
126
  const userRef = ensureRecordId(userRefSource, TABLES.USER)
127
127
  const userName = params.context.userName ?? 'User'
@@ -147,7 +147,7 @@ class AgentExecutorService {
147
147
 
148
148
  const mode = params.executionMode ?? 'linear'
149
149
 
150
- const dispatchMode = workstream.mode === 'group' ? 'fixedWorkstreamMode' : 'direct'
150
+ const dispatchMode = thread.type === 'group' ? 'fixedThreadMode' : 'direct'
151
151
  const dispatchInstructionSections = [
152
152
  buildOwnershipDispatchContextSection({
153
153
  node: params.nodeSpec,
@@ -161,8 +161,8 @@ class AgentExecutorService {
161
161
  await turnHooks.resolveAgent?.({
162
162
  agentId,
163
163
  mode: dispatchMode,
164
- workstream,
165
- workstreamRef,
164
+ thread,
165
+ threadRef,
166
166
  orgRef: organizationRef,
167
167
  userRef,
168
168
  userName,
@@ -176,7 +176,7 @@ class AgentExecutorService {
176
176
  const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
177
177
  const runtimeConfig = getAgentRuntimeConfig({
178
178
  agentId: resolvedAgentId,
179
- workstreamMode: workstream.mode,
179
+ threadType: thread.type,
180
180
  mode: dispatchMode,
181
181
  onboardingActive: false,
182
182
  linearInstalled: Boolean(linearInstallation),
@@ -192,9 +192,9 @@ class AgentExecutorService {
192
192
  orgId: organizationRef,
193
193
  userId: userRef,
194
194
  userName,
195
- workstreamId: workstreamRef,
195
+ threadId: threadRef,
196
196
  orgIdString: params.context.organizationId,
197
- workstreamMode: workstream.mode,
197
+ threadType: thread.type,
198
198
  mode: dispatchMode,
199
199
  linearInstalled: Boolean(linearInstallation),
200
200
  onboardingActive: false,
@@ -302,7 +302,6 @@ class AgentExecutorService {
302
302
  structuredOutput: finalResult.structuredOutput,
303
303
  artifacts: finalResult.artifacts,
304
304
  notes: 'Execution incomplete: missing required deliverables or validation failures.',
305
- quality: 'partial',
306
305
  }
307
306
  nodeWorkspaceService.cleanup(workspace)
308
307
  return result
@@ -312,7 +311,6 @@ class AgentExecutorService {
312
311
  structuredOutput: finalResult.structuredOutput,
313
312
  artifacts: finalResult.artifacts,
314
313
  notes: finalResult.notes,
315
- quality: finalResult.quality,
316
314
  }
317
315
 
318
316
  nodeWorkspaceService.cleanup(workspace)
@@ -2,7 +2,7 @@ import type { RecordIdRef } from '../db/record-id'
2
2
  import { recordIdToString } from '../db/record-id'
3
3
  import { TABLES } from '../db/tables'
4
4
  import { attachmentStorageService } from '../storage/attachment-storage.service'
5
- import type { UploadedWorkstreamAttachment as SdkUploadedWorkstreamAttachment } from '../storage/attachment-storage.service'
5
+ import type { UploadedThreadAttachment as SdkUploadedThreadAttachment } from '../storage/attachment-storage.service'
6
6
  import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachment-types'
7
7
 
8
8
  export type ReadableUploadMetadata = SdkReadableUploadMetadata
@@ -134,7 +134,7 @@ class AttachmentService {
134
134
  return attachmentStorageService.uploadOrganizationDocument({ file, orgId: toOrgId(orgId), namespace, relativePath })
135
135
  }
136
136
 
137
- async uploadWorkstreamAttachment({
137
+ async uploadThreadAttachment({
138
138
  file,
139
139
  orgId,
140
140
  userId,
@@ -142,12 +142,8 @@ class AttachmentService {
142
142
  file: File
143
143
  orgId: RecordIdRef
144
144
  userId: RecordIdRef
145
- }): Promise<SdkUploadedWorkstreamAttachment> {
146
- return attachmentStorageService.uploadWorkstreamAttachment({
147
- file,
148
- orgId: toOrgId(orgId),
149
- userId: toUserId(userId),
150
- })
145
+ }): Promise<SdkUploadedThreadAttachment> {
146
+ return attachmentStorageService.uploadThreadAttachment({ file, orgId: toOrgId(orgId), userId: toUserId(userId) })
151
147
  }
152
148
  }
153
149
 
@@ -30,15 +30,15 @@ import type { RecordIdInput } from '../db/record-id'
30
30
  import { databaseService } from '../db/service'
31
31
  import { TABLES } from '../db/tables'
32
32
  import type { AutonomousJobQueuePayload } from '../queues/autonomous-job.queue'
33
- import { extractMessageText } from '../runtime/workstream-chat-helpers'
33
+ import { extractMessageText } from '../runtime/thread-chat-helpers'
34
34
  import { buildAutonomousAtJobId, encodeBullmqId } from '../utils/autonomous-job-ids'
35
35
  import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
36
36
  import { compactRecord, compactWhitespace, stringifyUnknown, truncateText } from '../utils/string'
37
37
  import { executionPlanService } from './execution-plan.service'
38
38
  import { getNotificationService } from './notification.service'
39
39
  import { queueJobService } from './queue-job.service'
40
- import { runWorkstreamTurnInBackground } from './workstream-turn'
41
- import { workstreamService } from './workstream.service'
40
+ import { runThreadTurnInBackground } from './thread-turn'
41
+ import { threadService } from './thread.service'
42
42
 
43
43
  const AUTONOMOUS_JOB_QUEUE_NAME = 'autonomous-job'
44
44
 
@@ -51,7 +51,7 @@ const AutonomousJobRowSchema = z.object({
51
51
  organizationId: recordIdSchema,
52
52
  ownerUserId: recordIdSchema,
53
53
  ownerUserName: z.string().optional(),
54
- workstreamId: recordIdSchema,
54
+ threadId: recordIdSchema,
55
55
  agentId: z.string(),
56
56
  title: z.string(),
57
57
  prompt: z.string(),
@@ -72,7 +72,7 @@ const AutonomousJobRowSchema = z.object({
72
72
  const AutonomousJobRunRowSchema = z.object({
73
73
  id: recordIdSchema,
74
74
  autonomousJobId: recordIdSchema,
75
- workstreamId: recordIdSchema,
75
+ threadId: recordIdSchema,
76
76
  queueJobId: recordIdSchema.optional(),
77
77
  status: AutonomousJobRunStatusSchema,
78
78
  inputMessageId: z.string().optional(),
@@ -111,7 +111,7 @@ class AutonomousJobService {
111
111
  organizationId: recordIdToString(row.organizationId, TABLES.ORGANIZATION),
112
112
  ownerUserId: recordIdToString(row.ownerUserId, TABLES.USER),
113
113
  ownerUserName: row.ownerUserName,
114
- workstreamId: recordIdToString(row.workstreamId, TABLES.WORKSTREAM),
114
+ threadId: recordIdToString(row.threadId, TABLES.THREAD),
115
115
  agentId: row.agentId,
116
116
  title: row.title,
117
117
  prompt: row.prompt,
@@ -134,7 +134,7 @@ class AutonomousJobService {
134
134
  return AutonomousJobRunSchema.parse({
135
135
  id: recordIdToString(row.id, TABLES.AUTONOMOUS_JOB_RUN),
136
136
  autonomousJobId: recordIdToString(row.autonomousJobId, TABLES.AUTONOMOUS_JOB),
137
- workstreamId: recordIdToString(row.workstreamId, TABLES.WORKSTREAM),
137
+ threadId: recordIdToString(row.threadId, TABLES.THREAD),
138
138
  queueJobId: row.queueJobId ? recordIdToString(row.queueJobId, TABLES.QUEUE_JOB) : undefined,
139
139
  status: row.status,
140
140
  inputMessageId: row.inputMessageId,
@@ -175,7 +175,7 @@ class AutonomousJobService {
175
175
  kind: 'notify',
176
176
  params: {
177
177
  organizationId: string
178
- workstreamId: string
178
+ threadId: string
179
179
  title: string
180
180
  body: string
181
181
  severity: 'info' | 'warning'
@@ -204,7 +204,7 @@ class AutonomousJobService {
204
204
 
205
205
  private async createRunRow(params: {
206
206
  autonomousJobId: RecordIdInput
207
- workstreamId: RecordIdInput
207
+ threadId: RecordIdInput
208
208
  queueJobId?: RecordIdInput
209
209
  status?: AutonomousJobRunStatus
210
210
  }): Promise<AutonomousJobRunRow> {
@@ -214,7 +214,7 @@ class AutonomousJobService {
214
214
  runId,
215
215
  compactRecord({
216
216
  autonomousJobId: ensureRecordId(params.autonomousJobId, TABLES.AUTONOMOUS_JOB),
217
- workstreamId: ensureRecordId(params.workstreamId, TABLES.WORKSTREAM),
217
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
218
218
  queueJobId: params.queueJobId ? ensureRecordId(params.queueJobId, TABLES.QUEUE_JOB) : undefined,
219
219
  status: params.status ?? 'queued',
220
220
  assistantMessageIds: [],
@@ -293,8 +293,8 @@ class AutonomousJobService {
293
293
  if (row.schedule.kind === 'at') {
294
294
  const queuedRun = options.reusePendingAtRun
295
295
  ? ((await this.findRecoverableRunRow(row.id)) ??
296
- (await this.createRunRow({ autonomousJobId: row.id, workstreamId: row.workstreamId })))
297
- : await this.createRunRow({ autonomousJobId: row.id, workstreamId: row.workstreamId })
296
+ (await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })))
297
+ : await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })
298
298
  const { enqueueAutonomousJobRun } = await import('../queues/autonomous-job.queue')
299
299
  const enqueueResult = await enqueueAutonomousJobRun({
300
300
  payload: {
@@ -334,10 +334,11 @@ class AutonomousJobService {
334
334
  const parsed = CreateAutonomousJobInputSchema.parse(input)
335
335
  const organizationId = ensureRecordId(parsed.organizationId, TABLES.ORGANIZATION)
336
336
  const ownerUserId = ensureRecordId(parsed.ownerUserId, TABLES.USER)
337
- const workstream = await workstreamService.createWorkstream(ownerUserId, organizationId, {
338
- mode: 'group',
339
- core: false,
340
- title: parsed.workstreamTitle ?? parsed.title,
337
+ const thread = await threadService.createThread({
338
+ userId: ownerUserId,
339
+ organizationId,
340
+ type: 'group',
341
+ title: parsed.threadTitle ?? parsed.title,
341
342
  })
342
343
  const jobId = new RecordId(TABLES.AUTONOMOUS_JOB, Bun.randomUUIDv7())
343
344
  const nextRunAt = this.computeNextRunAt(parsed.schedule)
@@ -348,7 +349,7 @@ class AutonomousJobService {
348
349
  organizationId,
349
350
  ownerUserId,
350
351
  ownerUserName: parsed.ownerUserName,
351
- workstreamId: ensureRecordId(workstream.id, TABLES.WORKSTREAM),
352
+ threadId: ensureRecordId(thread.id, TABLES.THREAD),
352
353
  agentId: parsed.agentId,
353
354
  title: parsed.title,
354
355
  prompt: parsed.prompt,
@@ -409,7 +410,7 @@ class AutonomousJobService {
409
410
  await this.unscheduleRow(existing)
410
411
 
411
412
  if (parsed.title && compactWhitespace(parsed.title) !== compactWhitespace(existing.title)) {
412
- await workstreamService.updateTitle(existing.workstreamId, parsed.title)
413
+ await threadService.updateTitle(existing.threadId, parsed.title)
413
414
  }
414
415
 
415
416
  const nextRunAt = this.computeNextRunAt(parsed.schedule ?? existing.schedule)
@@ -465,7 +466,7 @@ class AutonomousJobService {
465
466
 
466
467
  async runNow(jobId: RecordIdInput): Promise<AutonomousJobRun> {
467
468
  const row = await this.getRow(jobId)
468
- const queuedRun = await this.createRunRow({ autonomousJobId: row.id, workstreamId: row.workstreamId })
469
+ const queuedRun = await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })
469
470
  const { enqueueAutonomousJobRun } = await import('../queues/autonomous-job.queue')
470
471
  const enqueueResult = await enqueueAutonomousJobRun({
471
472
  payload: {
@@ -501,7 +502,7 @@ class AutonomousJobService {
501
502
  async delete(jobId: RecordIdInput): Promise<AutonomousJob> {
502
503
  const row = await this.getRow(jobId)
503
504
  const cancelled = await this.cancel(row.id)
504
- await workstreamService.updateStatus(row.workstreamId, 'archived')
505
+ await threadService.updateStatus(row.threadId, 'archived')
505
506
  return cancelled
506
507
  }
507
508
 
@@ -526,7 +527,7 @@ class AutonomousJobService {
526
527
  ? await this.getRunRow(job.data.autonomousJobRunId)
527
528
  : await this.createRunRow({
528
529
  autonomousJobId: autonomousJobRow.id,
529
- workstreamId: autonomousJobRow.workstreamId,
530
+ threadId: autonomousJobRow.threadId,
530
531
  queueJobId,
531
532
  status: 'queued',
532
533
  })
@@ -546,18 +547,18 @@ class AutonomousJobService {
546
547
  )) ?? runRow
547
548
 
548
549
  try {
549
- const workstream = await workstreamService.getWorkstream(autonomousJobRow.workstreamId)
550
+ const thread = await threadService.getThread(autonomousJobRow.threadId)
550
551
  const inputMessage = this.buildSyntheticUserMessage(autonomousJobRow.prompt)
551
- const turnResult = await runWorkstreamTurnInBackground({
552
- workstream,
553
- workstreamRef: ensureRecordId(autonomousJobRow.workstreamId, TABLES.WORKSTREAM),
552
+ const turnResult = await runThreadTurnInBackground({
553
+ thread,
554
+ threadRef: ensureRecordId(autonomousJobRow.threadId, TABLES.THREAD),
554
555
  orgRef: ensureRecordId(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
555
556
  userRef: ensureRecordId(autonomousJobRow.ownerUserId, TABLES.USER),
556
557
  userName: autonomousJobRow.ownerUserName,
557
558
  agentIdOverride: autonomousJobRow.agentId,
558
559
  inputMessage,
559
560
  })
560
- const activePlan = await executionPlanService.getActivePlanForWorkstream(autonomousJobRow.workstreamId)
561
+ const activePlan = await executionPlanService.getActivePlanForThread(autonomousJobRow.threadId)
561
562
  const runStatus: AutonomousJobRunStatus = activePlan?.status === 'awaiting-human' ? 'awaiting-human' : 'completed'
562
563
  const summary = truncateText(
563
564
  turnResult.assistantMessages
@@ -611,7 +612,7 @@ class AutonomousJobService {
611
612
 
612
613
  await this.maybeNotify('notify', {
613
614
  organizationId: recordIdToString(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
614
- workstreamId: recordIdToString(autonomousJobRow.workstreamId, TABLES.WORKSTREAM),
615
+ threadId: recordIdToString(autonomousJobRow.threadId, TABLES.THREAD),
615
616
  severity: 'info',
616
617
  title: `${autonomousJobRow.title} completed`,
617
618
  body: summary,
@@ -664,7 +665,7 @@ class AutonomousJobService {
664
665
 
665
666
  await this.maybeNotify('notify', {
666
667
  organizationId: recordIdToString(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
667
- workstreamId: recordIdToString(autonomousJobRow.workstreamId, TABLES.WORKSTREAM),
668
+ threadId: recordIdToString(autonomousJobRow.threadId, TABLES.THREAD),
668
669
  severity: 'warning',
669
670
  title: autoPause
670
671
  ? `${autonomousJobRow.title} paused after repeated failures`
@@ -7,13 +7,13 @@ import { databaseService } from '../db/service'
7
7
  import { TABLES } from '../db/tables'
8
8
  import { getRedisConnection } from '../redis/connection-accessor'
9
9
  import { withRedisLeaseLock } from '../redis/redis-lease-lock'
10
- import { CONTEXT_WINDOW_TOKENS, WORKSTREAM_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
10
+ import { CONTEXT_WINDOW_TOKENS, THREAD_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
11
11
  import { contextCompactionRuntime, compactMemoryBlockSummary } from './context-compaction-runtime.singleton'
12
- import { workstreamMessageService } from './workstream-message.service'
13
- import { WorkstreamSchema } from './workstream.types'
12
+ import { threadMessageService } from './thread-message.service'
13
+ import { ThreadSchema } from './thread.types'
14
14
 
15
15
  interface PersistedCompactionMetrics {
16
- domain: 'workstream'
16
+ domain: 'thread'
17
17
  entityId: string
18
18
  inputChars: number
19
19
  outputChars: number
@@ -37,11 +37,8 @@ class ContextCompactionService {
37
37
  return contextCompactionRuntime.shouldCompactHistory(params)
38
38
  }
39
39
 
40
- async compactWorkstreamHistory(params: {
41
- workstreamId: RecordIdRef
42
- contextSize?: number
43
- }): Promise<{ compacted: boolean }> {
44
- const entityId = recordIdToString(params.workstreamId, TABLES.WORKSTREAM)
40
+ async compactThreadHistory(params: { threadId: RecordIdRef; contextSize?: number }): Promise<{ compacted: boolean }> {
41
+ const entityId = recordIdToString(params.threadId, TABLES.THREAD)
45
42
 
46
43
  return withRedisLeaseLock(
47
44
  {
@@ -52,24 +49,20 @@ class ContextCompactionService {
52
49
  label: 'context-compaction',
53
50
  },
54
51
  async () => {
55
- const workstream = await databaseService.findOne(
56
- TABLES.WORKSTREAM,
57
- { id: params.workstreamId },
58
- WorkstreamSchema,
59
- )
60
- if (!workstream) {
61
- throw new Error(`Workstream not found for compaction: ${entityId}`)
52
+ const thread = await databaseService.findOne(TABLES.THREAD, { id: params.threadId }, ThreadSchema)
53
+ if (!thread) {
54
+ throw new Error(`Thread not found for compaction: ${entityId}`)
62
55
  }
63
56
 
64
- const liveMessages = await workstreamMessageService.listMessagesAfterCursor(
65
- params.workstreamId,
66
- typeof workstream.lastCompactedMessageId === 'string' ? workstream.lastCompactedMessageId : undefined,
57
+ const liveMessages = await threadMessageService.listMessagesAfterCursor(
58
+ params.threadId,
59
+ typeof thread.lastCompactedMessageId === 'string' ? thread.lastCompactedMessageId : undefined,
67
60
  )
68
61
 
69
62
  const result = await contextCompactionRuntime.compactHistory({
70
- summaryText: typeof workstream.compactionSummary === 'string' ? workstream.compactionSummary : '',
63
+ summaryText: typeof thread.compactionSummary === 'string' ? thread.compactionSummary : '',
71
64
  liveMessages,
72
- tailMessageCount: WORKSTREAM_RAW_TAIL_MESSAGES,
65
+ tailMessageCount: THREAD_RAW_TAIL_MESSAGES,
73
66
  contextSize: params.contextSize,
74
67
  })
75
68
 
@@ -78,21 +71,18 @@ class ContextCompactionService {
78
71
  }
79
72
 
80
73
  if (result.compactedMessages.length > 0) {
81
- await workstreamMessageService.upsertMessages({
82
- workstreamId: params.workstreamId,
83
- messages: result.compactedMessages,
84
- })
74
+ await threadMessageService.upsertMessages({ threadId: params.threadId, messages: result.compactedMessages })
85
75
  }
86
76
 
87
77
  await databaseService.update(
88
- TABLES.WORKSTREAM,
89
- params.workstreamId,
78
+ TABLES.THREAD,
79
+ params.threadId,
90
80
  { compactionSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId },
91
- WorkstreamSchema,
81
+ ThreadSchema,
92
82
  )
93
83
 
94
84
  this.logCompactionMetrics({
95
- domain: 'workstream',
85
+ domain: 'thread',
96
86
  entityId,
97
87
  inputChars: result.inputChars,
98
88
  outputChars: result.outputChars,