@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
@@ -4,7 +4,7 @@ import { configureEmbeddingCache } from './ai/embedding-cache'
4
4
  import { configureAgentFactory, configureAgents } from './config/agent-defaults'
5
5
  import { configureBackgroundProcessing } from './config/background-processing'
6
6
  import { configureLotaLogger } from './config/logger'
7
- import { configureWorkstreams } from './config/workstream-defaults'
7
+ import { configureThreads } from './config/thread-defaults'
8
8
  import { ensureRecordId } from './db/record-id'
9
9
  import { computeSchemaFingerprint } from './db/schema-fingerprint'
10
10
  import { LOTA_SDK_DATABASE_NAME } from './db/sdk-database'
@@ -17,7 +17,7 @@ import { createRedisConnectionManager } from './redis/connection'
17
17
  import { setRedisConnectionManager } from './redis/index'
18
18
  import { closeSharedSubscriber } from './redis/stream-context'
19
19
  import type { isApprovalContinuationRequest } from './runtime/approval-continuation'
20
- import { routeWorkstreamChatMessages } from './runtime/chat-request-routing'
20
+ import { routeThreadChatMessages } from './runtime/chat-request-routing'
21
21
  import { configureGraphDesigner } from './runtime/graph-designer'
22
22
  import type { LotaPlugin, SystemNodeExecutor } from './runtime/plugin-types'
23
23
  import { configureRuntimeConfig, LOTA_RUNTIME_ENV_KEYS, parseLotaRuntimeConfig } from './runtime/runtime-config'
@@ -57,41 +57,41 @@ import {
57
57
  socialChatHistoryService as socialChatHistoryServiceSingleton,
58
58
  } from './services/social-chat-history.service'
59
59
  import { getBuiltInSystemExecutors } from './services/system-executor.service'
60
- import type { userService } from './services/user.service'
61
- import { userService as userServiceSingleton } from './services/user.service'
62
- import type { workstreamMessageService } from './services/workstream-message.service'
63
- import { workstreamMessageService as workstreamMessageServiceSingleton } from './services/workstream-message.service'
64
- import type { workstreamTitleService } from './services/workstream-title.service'
65
- import { workstreamTitleService as workstreamTitleServiceSingleton } from './services/workstream-title.service'
60
+ import type { threadMessageService } from './services/thread-message.service'
61
+ import { threadMessageService as threadMessageServiceSingleton } from './services/thread-message.service'
62
+ import type { threadTitleService } from './services/thread-title.service'
63
+ import { threadTitleService as threadTitleServiceSingleton } from './services/thread-title.service'
66
64
  import type {
67
- createWorkstreamApprovalContinuationStream,
68
- createWorkstreamNativeToolApprovalStream,
69
- createWorkstreamTurnStream,
70
- runWorkstreamTurnInBackground,
65
+ createThreadApprovalContinuationStream,
66
+ createThreadNativeToolApprovalStream,
67
+ createThreadTurnStream,
68
+ runThreadTurnInBackground,
71
69
  triggerPlanNodeTurn,
72
- } from './services/workstream-turn'
70
+ } from './services/thread-turn'
73
71
  import {
74
- createWorkstreamApprovalContinuationStream as createWorkstreamApprovalContinuationStreamSingleton,
75
- createWorkstreamNativeToolApprovalStream as createWorkstreamNativeToolApprovalStreamSingleton,
76
- createWorkstreamTurnStream as createWorkstreamTurnStreamSingleton,
72
+ createThreadApprovalContinuationStream as createThreadApprovalContinuationStreamSingleton,
73
+ createThreadNativeToolApprovalStream as createThreadNativeToolApprovalStreamSingleton,
74
+ createThreadTurnStream as createThreadTurnStreamSingleton,
77
75
  isApprovalContinuationRequest as isApprovalContinuationRequestSingleton,
78
- runWorkstreamTurnInBackground as runWorkstreamTurnInBackgroundSingleton,
76
+ runThreadTurnInBackground as runThreadTurnInBackgroundSingleton,
79
77
  triggerPlanNodeTurn as triggerPlanNodeTurnSingleton,
80
- } from './services/workstream-turn'
81
- import type { workstreamService } from './services/workstream.service'
82
- import { workstreamService as workstreamServiceSingleton } from './services/workstream.service'
78
+ } from './services/thread-turn'
79
+ import type { threadService } from './services/thread.service'
80
+ import { threadService as threadServiceSingleton } from './services/thread.service'
81
+ import type { userService } from './services/user.service'
82
+ import { userService as userServiceSingleton } from './services/user.service'
83
83
  import type { generatedDocumentStorageService } from './storage/generated-document-storage.service'
84
84
  import { generatedDocumentStorageService as generatedDocumentStorageServiceSingleton } from './storage/generated-document-storage.service'
85
85
 
86
- type ArchiveSdkWorkstream = (
87
- workstreamId: Parameters<typeof workstreamServiceSingleton.updateStatus>[0],
86
+ type ArchiveSdkThread = (
87
+ threadId: Parameters<typeof threadServiceSingleton.updateStatus>[0],
88
88
  status?: 'archived',
89
- ) => ReturnType<typeof workstreamServiceSingleton.updateStatus>
89
+ ) => ReturnType<typeof threadServiceSingleton.updateStatus>
90
90
 
91
- type UnarchiveSdkWorkstream = (
92
- workstreamId: Parameters<typeof workstreamServiceSingleton.updateStatus>[0],
93
- status?: 'regular',
94
- ) => ReturnType<typeof workstreamServiceSingleton.updateStatus>
91
+ type UnarchiveSdkThread = (
92
+ threadId: Parameters<typeof threadServiceSingleton.updateStatus>[0],
93
+ status?: 'active',
94
+ ) => ReturnType<typeof threadServiceSingleton.updateStatus>
95
95
 
96
96
  let activeRuntimeToken: symbol | null = null
97
97
 
@@ -131,14 +131,14 @@ export interface LotaRuntime {
131
131
  socialChatHistoryService: typeof socialChatHistoryServiceSingleton
132
132
  executionPlanService: typeof executionPlanService
133
133
  planAgentQueryService: typeof planAgentQueryService
134
- workstreamMessageService: typeof workstreamMessageService
135
- workstreamService: typeof workstreamService
136
- workstreamTitleService: typeof workstreamTitleService
137
- createWorkstreamApprovalContinuationStream: typeof createWorkstreamApprovalContinuationStream
138
- createWorkstreamNativeToolApprovalStream: typeof createWorkstreamNativeToolApprovalStream
139
- createWorkstreamTurnStream: typeof createWorkstreamTurnStream
134
+ threadMessageService: typeof threadMessageService
135
+ threadService: typeof threadService
136
+ threadTitleService: typeof threadTitleService
137
+ createThreadApprovalContinuationStream: typeof createThreadApprovalContinuationStream
138
+ createThreadNativeToolApprovalStream: typeof createThreadNativeToolApprovalStream
139
+ createThreadTurnStream: typeof createThreadTurnStream
140
140
  isApprovalContinuationRequest: typeof isApprovalContinuationRequest
141
- runWorkstreamTurnInBackground: typeof runWorkstreamTurnInBackground
141
+ runThreadTurnInBackground: typeof runThreadTurnInBackground
142
142
  triggerPlanNodeTurn: typeof triggerPlanNodeTurn
143
143
  }
144
144
  lota: {
@@ -164,32 +164,32 @@ export interface LotaRuntime {
164
164
  remove: typeof organizationMemberServiceSingleton.removeMembership
165
165
  isMember: typeof organizationMemberServiceSingleton.isMember
166
166
  }
167
- workstreams: {
168
- create: typeof workstreamServiceSingleton.createWorkstream
169
- list: typeof workstreamServiceSingleton.listWorkstreams
170
- get: typeof workstreamServiceSingleton.getWorkstream
171
- update: typeof workstreamServiceSingleton.updateTitle
172
- archive: ArchiveSdkWorkstream
173
- unarchive: UnarchiveSdkWorkstream
174
- delete: typeof workstreamServiceSingleton.deleteWorkstream
175
- stop: typeof workstreamServiceSingleton.stopActiveRun
176
- listMessages: typeof workstreamMessageServiceSingleton.listMessageHistoryPage
177
- getMessage: (params: { workstreamId: string; messageId: string }) => Promise<ChatMessage>
167
+ threads: {
168
+ create: typeof threadServiceSingleton.createThread
169
+ list: typeof threadServiceSingleton.listThreads
170
+ get: typeof threadServiceSingleton.getThread
171
+ update: typeof threadServiceSingleton.updateTitle
172
+ archive: ArchiveSdkThread
173
+ unarchive: UnarchiveSdkThread
174
+ delete: typeof threadServiceSingleton.deleteThread
175
+ stop: typeof threadServiceSingleton.stopActiveRun
176
+ listMessages: typeof threadMessageServiceSingleton.listMessageHistoryPage
177
+ getMessage: (params: { threadId: string; messageId: string }) => Promise<ChatMessage>
178
178
  sendMessage: (params: {
179
- workstreamId: string
179
+ threadId: string
180
180
  organizationId: string
181
181
  userId: string
182
182
  userName: string
183
- messages: Parameters<typeof routeWorkstreamChatMessages>[0]
184
- }) => Promise<Awaited<ReturnType<typeof createWorkstreamTurnStream>>>
183
+ messages: Parameters<typeof routeThreadChatMessages>[0]
184
+ }) => Promise<Awaited<ReturnType<typeof createThreadTurnStream>>>
185
185
  continueApproval: (params: {
186
- workstreamId: string
186
+ threadId: string
187
187
  organizationId: string
188
188
  userId: string
189
189
  userName: string
190
- messages: Parameters<typeof routeWorkstreamChatMessages>[0]
191
- }) => Promise<Awaited<ReturnType<typeof createWorkstreamApprovalContinuationStream>>>
192
- uploadAttachment: typeof attachmentServiceSingleton.uploadWorkstreamAttachment
190
+ messages: Parameters<typeof routeThreadChatMessages>[0]
191
+ }) => Promise<Awaited<ReturnType<typeof createThreadApprovalContinuationStream>>>
192
+ uploadAttachment: typeof attachmentServiceSingleton.uploadThreadAttachment
193
193
  }
194
194
  }
195
195
  redis: {
@@ -253,7 +253,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
253
253
  descriptions: runtimeConfig.agents.descriptions,
254
254
  routerModelId: runtimeConfig.agents.routerModelId,
255
255
  teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
256
- getCoreWorkstreamProfile: runtimeConfig.agents.getCoreWorkstreamProfile,
256
+ getCoreThreadProfile: runtimeConfig.agents.getCoreThreadProfile,
257
257
  })
258
258
  configureAgentFactory({
259
259
  createAgent: runtimeConfig.agents.createAgent,
@@ -261,7 +261,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
261
261
  getAgentRuntimeConfig: runtimeConfig.agents.getAgentRuntimeConfig,
262
262
  pluginRuntime: runtimeConfig.pluginRuntime,
263
263
  })
264
- configureWorkstreams({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.workstreams })
264
+ configureThreads({ agentRoster: runtimeConfig.agents.roster, config: runtimeConfig.threads })
265
265
  configureNotificationService(runtimeConfig.notificationService ?? null)
266
266
  configureRuntimeExtensions({
267
267
  adapters: runtimeConfig.runtimeAdapters,
@@ -311,65 +311,61 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
311
311
  remove: organizationMemberServiceSingleton.removeMembership.bind(organizationMemberServiceSingleton),
312
312
  isMember: organizationMemberServiceSingleton.isMember.bind(organizationMemberServiceSingleton),
313
313
  },
314
- workstreams: {
315
- create: workstreamServiceSingleton.createWorkstream.bind(workstreamServiceSingleton),
316
- list: workstreamServiceSingleton.listWorkstreams.bind(workstreamServiceSingleton),
317
- get: workstreamServiceSingleton.getWorkstream.bind(workstreamServiceSingleton),
318
- update: workstreamServiceSingleton.updateTitle.bind(workstreamServiceSingleton),
319
- archive: async (workstreamId, status = 'archived') =>
320
- await workstreamServiceSingleton.updateStatus(workstreamId, status),
321
- unarchive: async (workstreamId, status = 'regular') =>
322
- await workstreamServiceSingleton.updateStatus(workstreamId, status),
323
- delete: workstreamServiceSingleton.deleteWorkstream.bind(workstreamServiceSingleton),
324
- stop: workstreamServiceSingleton.stopActiveRun.bind(workstreamServiceSingleton),
325
- listMessages: workstreamMessageServiceSingleton.listMessageHistoryPage.bind(workstreamMessageServiceSingleton),
326
- getMessage: async ({ workstreamId, messageId }) => {
327
- const messages = await workstreamMessageServiceSingleton.listMessages(
328
- ensureRecordId(workstreamId, TABLES.WORKSTREAM),
329
- )
314
+ threads: {
315
+ create: threadServiceSingleton.createThread.bind(threadServiceSingleton),
316
+ list: threadServiceSingleton.listThreads.bind(threadServiceSingleton),
317
+ get: threadServiceSingleton.getThread.bind(threadServiceSingleton),
318
+ update: threadServiceSingleton.updateTitle.bind(threadServiceSingleton),
319
+ archive: async (threadId, status = 'archived') => await threadServiceSingleton.updateStatus(threadId, status),
320
+ unarchive: async (threadId, status = 'active') => await threadServiceSingleton.updateStatus(threadId, status),
321
+ delete: threadServiceSingleton.deleteThread.bind(threadServiceSingleton),
322
+ stop: threadServiceSingleton.stopActiveRun.bind(threadServiceSingleton),
323
+ listMessages: threadMessageServiceSingleton.listMessageHistoryPage.bind(threadMessageServiceSingleton),
324
+ getMessage: async ({ threadId, messageId }) => {
325
+ const messages = await threadMessageServiceSingleton.listMessages(ensureRecordId(threadId, TABLES.THREAD))
330
326
  const message = messages.find((candidate) => candidate.id === messageId)
331
327
  if (!message) {
332
- throw new Error(`Workstream message not found: ${messageId}`)
328
+ throw new Error(`Thread message not found: ${messageId}`)
333
329
  }
334
330
  return message
335
331
  },
336
- sendMessage: async ({ workstreamId, organizationId, userId, userName, messages }) => {
337
- const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
338
- const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
339
- const routed = routeWorkstreamChatMessages(messages)
332
+ sendMessage: async ({ threadId, organizationId, userId, userName, messages }) => {
333
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
334
+ const thread = await threadServiceSingleton.getThread(threadRef)
335
+ const routed = routeThreadChatMessages(messages)
340
336
  if (routed.kind !== 'turn') {
341
337
  throw new Error(routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.')
342
338
  }
343
339
 
344
- return createWorkstreamTurnStreamSingleton({
345
- workstream,
346
- workstreamRef,
340
+ return createThreadTurnStreamSingleton({
341
+ thread,
342
+ threadRef,
347
343
  orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
348
344
  userRef: ensureRecordId(userId, TABLES.USER),
349
345
  userName,
350
346
  inputMessage: routed.inputMessage,
351
347
  })
352
348
  },
353
- continueApproval: async ({ workstreamId, organizationId, userId, userName, messages }) => {
354
- const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
355
- const workstream = await workstreamServiceSingleton.getWorkstream(workstreamRef)
356
- const routed = routeWorkstreamChatMessages(messages)
349
+ continueApproval: async ({ threadId, organizationId, userId, userName, messages }) => {
350
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
351
+ const thread = await threadServiceSingleton.getThread(threadRef)
352
+ const routed = routeThreadChatMessages(messages)
357
353
  if (routed.kind !== 'approval-continuation') {
358
354
  throw new Error(
359
355
  routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
360
356
  )
361
357
  }
362
358
 
363
- return createWorkstreamApprovalContinuationStreamSingleton({
364
- workstream,
365
- workstreamRef,
359
+ return createThreadApprovalContinuationStreamSingleton({
360
+ thread,
361
+ threadRef,
366
362
  orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
367
363
  userRef: ensureRecordId(userId, TABLES.USER),
368
364
  userName,
369
365
  approvalMessages: routed.approvalMessages,
370
366
  })
371
367
  },
372
- uploadAttachment: attachmentServiceSingleton.uploadWorkstreamAttachment.bind(attachmentServiceSingleton),
368
+ uploadAttachment: attachmentServiceSingleton.uploadThreadAttachment.bind(attachmentServiceSingleton),
373
369
  },
374
370
  } satisfies LotaRuntime['lota']
375
371
 
@@ -395,14 +391,14 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
395
391
  socialChatHistoryService: socialChatHistoryServiceSingleton,
396
392
  executionPlanService: executionPlanServiceSingleton,
397
393
  planAgentQueryService: planAgentQueryServiceSingleton,
398
- workstreamMessageService: workstreamMessageServiceSingleton,
399
- workstreamService: workstreamServiceSingleton,
400
- workstreamTitleService: workstreamTitleServiceSingleton,
401
- createWorkstreamApprovalContinuationStream: createWorkstreamApprovalContinuationStreamSingleton,
402
- createWorkstreamNativeToolApprovalStream: createWorkstreamNativeToolApprovalStreamSingleton,
403
- createWorkstreamTurnStream: createWorkstreamTurnStreamSingleton,
394
+ threadMessageService: threadMessageServiceSingleton,
395
+ threadService: threadServiceSingleton,
396
+ threadTitleService: threadTitleServiceSingleton,
397
+ createThreadApprovalContinuationStream: createThreadApprovalContinuationStreamSingleton,
398
+ createThreadNativeToolApprovalStream: createThreadNativeToolApprovalStreamSingleton,
399
+ createThreadTurnStream: createThreadTurnStreamSingleton,
404
400
  isApprovalContinuationRequest: isApprovalContinuationRequestSingleton,
405
- runWorkstreamTurnInBackground: runWorkstreamTurnInBackgroundSingleton,
401
+ runThreadTurnInBackground: runThreadTurnInBackgroundSingleton,
406
402
  triggerPlanNodeTurn: triggerPlanNodeTurnSingleton,
407
403
  },
408
404
  lota,
@@ -456,7 +452,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
456
452
  function getBuiltInSchemaFiles(): URL[] {
457
453
  return [
458
454
  new URL('../infrastructure/schema/00_identity.surql', import.meta.url),
459
- new URL('../infrastructure/schema/00_workstream.surql', import.meta.url),
455
+ new URL('../infrastructure/schema/00_thread.surql', import.meta.url),
460
456
  new URL('../infrastructure/schema/01_memory.surql', import.meta.url),
461
457
  new URL('../infrastructure/schema/02_execution_plan.surql', import.meta.url),
462
458
  new URL('../infrastructure/schema/03_learned_skill.surql', import.meta.url),
@@ -8,13 +8,18 @@ export interface RecordIdShape {
8
8
  id: RecordIdValue
9
9
  }
10
10
 
11
- export interface StringableRecordId {
11
+ export type RecordIdInput = string | RecordId | StringRecordId | RecordIdShape
12
+ export type RecordIdRef = RecordIdInput
13
+
14
+ const SURREAL_RECORD_ID_CLASS_NAMES = new Set(['RecordId', 'StringRecordId'])
15
+
16
+ interface SurrealRecordIdLike {
12
17
  toString(): string
13
18
  }
14
19
 
15
- export type RecordIdInput = string | RecordId | StringRecordId | RecordIdShape | StringableRecordId
16
-
17
- export type RecordIdRef = RecordIdInput
20
+ interface NamedConstructor {
21
+ name?: unknown
22
+ }
18
23
 
19
24
  class InvalidRecordIdError extends BadRequestError {
20
25
  constructor(message: string) {
@@ -23,18 +28,18 @@ class InvalidRecordIdError extends BadRequestError {
23
28
  }
24
29
  }
25
30
 
26
- export function readCustomStringValue(value: object): string | null {
27
- const toStringValue: unknown = Reflect.get(value, 'toString')
28
- if (typeof toStringValue !== 'function' || toStringValue === Object.prototype.toString) {
29
- return null
31
+ export function isSurrealRecordIdValue(value: unknown): boolean {
32
+ if (!value || typeof value !== 'object') {
33
+ return false
30
34
  }
31
35
 
32
- const stringValue = Reflect.apply(toStringValue as (this: object) => unknown, value, [])
33
- if (typeof stringValue !== 'string' || stringValue.length === 0) {
34
- return null
36
+ const constructor = (value as { constructor?: unknown }).constructor
37
+ if (typeof constructor !== 'function') {
38
+ return false
35
39
  }
36
40
 
37
- return stringValue
41
+ const constructorName = (constructor as NamedConstructor).name
42
+ return typeof constructorName === 'string' && SURREAL_RECORD_ID_CLASS_NAMES.has(constructorName)
38
43
  }
39
44
 
40
45
  export function ensureRecordId(value: RecordIdInput, fallbackTable?: string): RecordId {
@@ -42,8 +47,8 @@ export function ensureRecordId(value: RecordIdInput, fallbackTable?: string): Re
42
47
  return value
43
48
  }
44
49
 
45
- if (value instanceof StringRecordId) {
46
- return ensureRecordId(value.toString(), fallbackTable)
50
+ if (value instanceof StringRecordId || isSurrealRecordIdValue(value)) {
51
+ return ensureRecordId((value as SurrealRecordIdLike).toString(), fallbackTable)
47
52
  }
48
53
 
49
54
  if (typeof value === 'string') {
@@ -58,16 +63,11 @@ export function ensureRecordId(value: RecordIdInput, fallbackTable?: string): Re
58
63
  return new RecordId(fallbackTable, value)
59
64
  }
60
65
 
61
- if (typeof value === 'object') {
66
+ if (typeof value === 'object' && Object.keys(value).length === 2) {
62
67
  const record = value as { tb?: string; id?: RecordIdValue }
63
- if (record.tb && record.id !== undefined) {
68
+ if (typeof record.tb === 'string' && record.id !== undefined) {
64
69
  return new RecordId(record.tb, record.id)
65
70
  }
66
-
67
- const stringValue = readCustomStringValue(value)
68
- if (stringValue && stringValue !== '[object Object]') {
69
- return ensureRecordId(stringValue, fallbackTable)
70
- }
71
71
  }
72
72
 
73
73
  throw new InvalidRecordIdError('Invalid record id value')
package/src/db/service.ts CHANGED
@@ -16,7 +16,7 @@ import { serverLogger } from '../config/logger'
16
16
  import { withTimeout } from '../utils/async'
17
17
  import { isRecord } from '../utils/string'
18
18
  import type { RecordIdInput } from './record-id'
19
- import { ensureRecordId, readCustomStringValue } from './record-id'
19
+ import { ensureRecordId, isSurrealRecordIdValue } from './record-id'
20
20
  import type { DatabaseTable } from './tables'
21
21
 
22
22
  export class SurrealDBError extends Error {
@@ -103,19 +103,6 @@ function isBoundQueryLike(value: unknown): value is BoundQueryLike {
103
103
  return value.bindings === undefined || isRecord(value.bindings)
104
104
  }
105
105
 
106
- function toStringLikeValue(value: unknown): string | null {
107
- if (!value || typeof value !== 'object') {
108
- return null
109
- }
110
-
111
- const stringValue = readCustomStringValue(value)
112
- if (typeof stringValue !== 'string' || stringValue.length === 0 || stringValue === '[object Object]') {
113
- return null
114
- }
115
-
116
- return stringValue
117
- }
118
-
119
106
  const CONNECT_MAX_ATTEMPTS = 5
120
107
  const CONNECT_RETRY_BASE_DELAY_MS = 100
121
108
  const CONNECT_RETRY_JITTER_MS = 50
@@ -353,6 +340,10 @@ export class SurrealDBService {
353
340
  return value.map((entry) => this.normalizeParseValue(entry))
354
341
  }
355
342
 
343
+ if (isSurrealRecordIdValue(value)) {
344
+ return ensureRecordId(value as RecordIdInput)
345
+ }
346
+
356
347
  if (!isRecord(value)) {
357
348
  return value
358
349
  }
@@ -413,10 +404,6 @@ export class SurrealDBService {
413
404
  return new BoundQuery(query.query, this.normalizeBindings(query.bindings))
414
405
  }
415
406
 
416
- private isSerializedRecordId(value: string): boolean {
417
- return /^[a-zA-Z][a-zA-Z0-9_]*:(?:⟨.+⟩|.+)$/.test(value)
418
- }
419
-
420
407
  private normalizeRuntimeValue(value: unknown): unknown {
421
408
  if (value === null || value === undefined) {
422
409
  return value
@@ -435,17 +422,16 @@ export class SurrealDBService {
435
422
  return value.map((entry) => this.normalizeRuntimeValue(entry))
436
423
  }
437
424
 
425
+ if (isSurrealRecordIdValue(value)) {
426
+ return ensureRecordId(value as RecordIdInput)
427
+ }
428
+
438
429
  if (!isRecord(value)) {
439
430
  return value
440
431
  }
441
432
 
442
433
  if ('tb' in value && 'id' in value && Object.keys(value).length === 2) {
443
- return ensureRecordId(value as RecordIdInput)
444
- }
445
-
446
- const stringValue = toStringLikeValue(value)
447
- if (stringValue && this.isSerializedRecordId(stringValue)) {
448
- return ensureRecordId(stringValue)
434
+ return ensureRecordId(value as unknown as RecordIdInput)
449
435
  }
450
436
 
451
437
  const entries = Object.entries(value)
@@ -467,17 +453,23 @@ export class SurrealDBService {
467
453
  }
468
454
 
469
455
  private normalizeMutationFieldValue(value: unknown): unknown {
470
- if (value === null || value === undefined) {
456
+ if (value === undefined) {
471
457
  return undefined
472
458
  }
473
459
 
460
+ if (value === null) {
461
+ return null
462
+ }
463
+
474
464
  return this.normalizeRuntimeValue(value)
475
465
  }
476
466
 
477
467
  // Cast is safe: normalizeRuntimeValue preserves Record shape when input is a Record
478
468
  private normalizeMutationData(data: Record<string, unknown>): Record<string, unknown> {
479
469
  return Object.fromEntries(
480
- Object.entries(data).map(([key, value]) => [key, this.normalizeMutationFieldValue(value)]),
470
+ Object.entries(data)
471
+ .map(([key, value]) => [key, this.normalizeMutationFieldValue(value)] as const)
472
+ .filter((entry): entry is readonly [string, unknown] => entry[1] !== undefined),
481
473
  ) as Record<string, unknown>
482
474
  }
483
475
 
@@ -490,11 +482,6 @@ export class SurrealDBService {
490
482
  return new Table(value)
491
483
  }
492
484
 
493
- const stringValue = toStringLikeValue(value)
494
- if (stringValue) {
495
- return new Table(stringValue)
496
- }
497
-
498
485
  throw new SurrealDBError('Invalid table value')
499
486
  }
500
487
 
@@ -503,20 +490,17 @@ export class SurrealDBService {
503
490
  return true
504
491
  }
505
492
 
493
+ if (isSurrealRecordIdValue(value)) {
494
+ return true
495
+ }
496
+
506
497
  if (typeof value === 'string') {
507
498
  return /^[a-zA-Z][a-zA-Z0-9_]*:/.test(value)
508
499
  }
509
500
 
510
501
  if (value && typeof value === 'object') {
511
502
  const record = value as { tb?: unknown; id?: unknown }
512
- if (typeof record.tb === 'string' && record.id !== undefined && Object.keys(value).length === 2) {
513
- return true
514
- }
515
-
516
- const stringValue = toStringLikeValue(value)
517
- if (stringValue) {
518
- return /^[a-zA-Z][a-zA-Z0-9_]*:/.test(stringValue)
519
- }
503
+ return typeof record.tb === 'string' && record.id !== undefined && Object.keys(value).length === 2
520
504
  }
521
505
 
522
506
  return false
@@ -689,7 +673,11 @@ export class SurrealDBService {
689
673
  for (const key of filterKeys) {
690
674
  this.assertValidIdentifier(key, 'filter field')
691
675
  }
692
- const orderDir = options?.orderDir ?? 'ASC'
676
+ const rawOrderDir: unknown = options?.orderDir
677
+ if (rawOrderDir !== undefined && rawOrderDir !== 'ASC' && rawOrderDir !== 'DESC') {
678
+ throw new SurrealDBError(`Invalid orderDir value: ${this.describeInvalidValue(rawOrderDir)}`)
679
+ }
680
+ const orderDir = rawOrderDir ?? 'ASC'
693
681
  const limit = options?.limit
694
682
  const offset = options?.offset
695
683
  const vars: Record<string, unknown> = this.normalizeMutationData(filter)
@@ -730,6 +718,22 @@ export class SurrealDBService {
730
718
  }
731
719
  }
732
720
 
721
+ private describeInvalidValue(value: unknown): string {
722
+ if (typeof value === 'string') {
723
+ return value
724
+ }
725
+
726
+ try {
727
+ const serialized = JSON.stringify(value)
728
+ if (typeof serialized === 'string') {
729
+ return serialized
730
+ }
731
+ return Object.prototype.toString.call(value)
732
+ } catch {
733
+ return Object.prototype.toString.call(value)
734
+ }
735
+ }
736
+
733
737
  async create<T extends z.ZodTypeAny>(
734
738
  table: DatabaseTable,
735
739
  data: Record<string, unknown>,
package/src/db/tables.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export const TABLES = {
2
- WORKSTREAM_MESSAGE: 'workstreamMessage',
3
- WORKSTREAM: 'workstream',
2
+ THREAD: 'thread',
3
+ THREAD_MESSAGE: 'threadMessage',
4
+ THREAD_ATTACHMENT: 'threadAttachment',
4
5
  RUNTIME_BOOTSTRAP: 'runtimeBootstrap',
5
- WORKSTREAM_ATTACHMENT: 'workstreamAttachment',
6
6
  MEMORY: 'memory',
7
7
  MEMORY_RELATION: 'memoryRelation',
8
8
  MEMORY_HISTORY: 'memoryHistory',
@@ -1,9 +1,9 @@
1
1
  import { recordIdSchema } from '@lota-sdk/shared'
2
2
  import { z } from 'zod'
3
3
 
4
- export const WorkstreamMessageRowSchema = z.object({
4
+ export const ThreadMessageRowSchema = z.object({
5
5
  id: recordIdSchema,
6
- workstreamId: recordIdSchema,
6
+ threadId: recordIdSchema,
7
7
  messageId: z.string(),
8
8
  role: z.enum(['system', 'user', 'assistant']),
9
9
  parts: z.array(z.record(z.string(), z.unknown())).optional(),
@@ -12,4 +12,4 @@ export const WorkstreamMessageRowSchema = z.object({
12
12
  updatedAt: z.coerce.date().optional(),
13
13
  })
14
14
 
15
- export type WorkstreamMessageRow = z.infer<typeof WorkstreamMessageRowSchema>
15
+ export type ThreadMessageRow = z.infer<typeof ThreadMessageRowSchema>
@@ -4,11 +4,11 @@ import { ensureRecordId } from '../db/record-id'
4
4
  import { databaseService } from '../db/service'
5
5
  import { TABLES } from '../db/tables'
6
6
  import { contextCompactionService } from '../services/context-compaction.service'
7
- import { workstreamService } from '../services/workstream.service'
7
+ import { threadService } from '../services/thread.service'
8
8
  import { createQueueFactory } from './queue-factory'
9
9
 
10
10
  interface ContextCompactionJob {
11
- domain: 'workstream'
11
+ domain: 'thread'
12
12
  entityId: string
13
13
  contextSize?: number
14
14
  }
@@ -17,12 +17,12 @@ async function processContextCompactionJob(job: Job<ContextCompactionJob>): Prom
17
17
  await databaseService.connect()
18
18
 
19
19
  const { entityId, contextSize } = job.data
20
- const workstreamRef = ensureRecordId(entityId, TABLES.WORKSTREAM)
21
- await workstreamService.setCompacting(workstreamRef, true)
20
+ const threadRef = ensureRecordId(entityId, TABLES.THREAD)
21
+ await threadService.setCompacting(threadRef, true)
22
22
  try {
23
- await contextCompactionService.compactWorkstreamHistory({ workstreamId: workstreamRef, contextSize })
23
+ await contextCompactionService.compactThreadHistory({ threadId: threadRef, contextSize })
24
24
  } finally {
25
- await workstreamService.setCompacting(workstreamRef, false)
25
+ await threadService.setCompacting(threadRef, false)
26
26
  }
27
27
  }
28
28
 
@@ -10,7 +10,7 @@ import { createQueueFactory } from './queue-factory'
10
10
  export interface PlanAgentHeartbeatWakeJob {
11
11
  type: 'wake-node'
12
12
  organizationId: string
13
- workstreamId: string
13
+ threadId: string
14
14
  runId: string
15
15
  nodeId: string
16
16
  agentId: string
@@ -58,7 +58,7 @@ const planAgentHeartbeatQueue = createQueueFactory<PlanAgentHeartbeatJob>({
58
58
 
59
59
  function buildWakeJobId(params: {
60
60
  organizationId: string
61
- workstreamId: string
61
+ threadId: string
62
62
  runId: string
63
63
  nodeId: string
64
64
  agentId: string
@@ -70,7 +70,7 @@ function buildWakeJobId(params: {
70
70
 
71
71
  export async function enqueuePlanAgentHeartbeatWake(params: {
72
72
  organizationId: string
73
- workstreamId: string
73
+ threadId: string
74
74
  runId: string
75
75
  nodeId: string
76
76
  agentId: string
@@ -13,7 +13,7 @@ interface PostChatMemoryMessage {
13
13
 
14
14
  interface PostChatMemoryExtractionJob {
15
15
  orgId: string
16
- workstreamId: string
16
+ threadId: string
17
17
  sourceId: string
18
18
  source?: string
19
19
  sourceMetadata?: Record<string, unknown>