@lota-sdk/core 0.1.14 → 0.1.16

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 (174) hide show
  1. package/infrastructure/schema/00_identity.surql +0 -2
  2. package/infrastructure/schema/01_memory.surql +1 -1
  3. package/infrastructure/schema/02_execution_plan.surql +62 -1
  4. package/infrastructure/schema/03_learned_skill.surql +1 -1
  5. package/infrastructure/schema/06_playbook.surql +25 -0
  6. package/infrastructure/schema/07_institutional_memory.surql +13 -0
  7. package/infrastructure/schema/08_quality_metrics.surql +17 -0
  8. package/package.json +9 -8
  9. package/src/ai/definitions.ts +80 -2
  10. package/src/ai/embedding-cache.ts +7 -6
  11. package/src/ai/index.ts +0 -1
  12. package/src/bifrost/bifrost.ts +14 -14
  13. package/src/config/agent-defaults.ts +32 -22
  14. package/src/config/agent-types.ts +11 -0
  15. package/src/config/constants.ts +2 -14
  16. package/src/config/debug-logger.ts +5 -1
  17. package/src/config/index.ts +3 -0
  18. package/src/config/logger.ts +7 -9
  19. package/src/config/model-constants.ts +16 -34
  20. package/src/config/search.ts +1 -15
  21. package/src/create-runtime.ts +453 -0
  22. package/src/db/cursor-pagination.ts +3 -6
  23. package/src/db/index.ts +2 -0
  24. package/src/db/memory-store.rows.ts +7 -7
  25. package/src/db/memory-store.ts +24 -24
  26. package/src/db/memory.ts +18 -16
  27. package/src/db/schema-fingerprint.ts +1 -0
  28. package/src/db/service.ts +193 -122
  29. package/src/db/startup.ts +9 -13
  30. package/src/db/surreal-mutation.ts +43 -0
  31. package/src/db/tables.ts +7 -0
  32. package/src/db/workstream-message-row.ts +15 -0
  33. package/src/embeddings/provider.ts +1 -1
  34. package/src/index.ts +1 -1
  35. package/src/queues/context-compaction.queue.ts +17 -52
  36. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  37. package/src/queues/document-processor.queue.ts +7 -7
  38. package/src/queues/index.ts +3 -0
  39. package/src/queues/memory-consolidation.queue.ts +18 -54
  40. package/src/queues/plan-scheduler.queue.ts +97 -0
  41. package/src/queues/post-chat-memory.queue.ts +15 -60
  42. package/src/queues/queue-factory.ts +100 -0
  43. package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
  44. package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
  45. package/src/queues/skill-extraction.queue.ts +15 -50
  46. package/src/queues/workstream-title-generation.queue.ts +15 -51
  47. package/src/redis/connection.ts +12 -3
  48. package/src/redis/index.ts +2 -1
  49. package/src/redis/org-memory-lock.ts +1 -1
  50. package/src/redis/redis-lease-lock.ts +41 -8
  51. package/src/redis/stream-context.ts +11 -0
  52. package/src/runtime/agent-runtime-policy.ts +106 -21
  53. package/src/runtime/agent-stream-helpers.ts +2 -1
  54. package/src/runtime/approval-continuation.ts +12 -6
  55. package/src/runtime/context-compaction-constants.ts +1 -1
  56. package/src/runtime/context-compaction-runtime.ts +7 -5
  57. package/src/runtime/context-compaction.ts +40 -97
  58. package/src/runtime/execution-plan.ts +23 -19
  59. package/src/runtime/graph-designer.ts +15 -0
  60. package/src/runtime/helper-model.ts +10 -196
  61. package/src/runtime/index.ts +14 -1
  62. package/src/runtime/llm-content.ts +1 -1
  63. package/src/runtime/memory-block.ts +11 -12
  64. package/src/runtime/memory-pipeline.ts +26 -10
  65. package/src/runtime/plugin-resolution.ts +35 -0
  66. package/src/runtime/plugin-types.ts +73 -1
  67. package/src/runtime/retrieval-adapters.ts +1 -1
  68. package/src/runtime/runtime-config.ts +25 -12
  69. package/src/runtime/runtime-extensions.ts +91 -15
  70. package/src/runtime/runtime-worker-registry.ts +6 -0
  71. package/src/runtime/team-consultation-orchestrator.ts +45 -28
  72. package/src/runtime/team-consultation-prompts.ts +11 -2
  73. package/src/runtime/title-helpers.ts +11 -4
  74. package/src/runtime/workstream-chat-helpers.ts +6 -7
  75. package/src/runtime/workstream-routing-policy.ts +0 -30
  76. package/src/runtime/workstream-state.ts +17 -7
  77. package/src/services/adaptive-playbook.service.ts +152 -0
  78. package/src/services/agent-executor.service.ts +293 -0
  79. package/src/services/artifact-provenance.service.ts +172 -0
  80. package/src/services/attachment.service.ts +7 -12
  81. package/src/services/context-compaction.service.ts +75 -58
  82. package/src/services/context-enrichment.service.ts +33 -0
  83. package/src/services/coordination-registry.service.ts +117 -0
  84. package/src/services/document-chunk.service.ts +38 -33
  85. package/src/services/domain-agent-executor.service.ts +71 -0
  86. package/src/services/execution-plan.service.ts +271 -50
  87. package/src/services/feedback-loop.service.ts +96 -0
  88. package/src/services/global-orchestrator.service.ts +148 -0
  89. package/src/services/index.ts +26 -0
  90. package/src/services/institutional-memory.service.ts +145 -0
  91. package/src/services/learned-skill.service.ts +30 -15
  92. package/src/services/memory-assessment.service.ts +3 -2
  93. package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
  94. package/src/services/memory.service.ts +55 -69
  95. package/src/services/monitoring-window.service.ts +86 -0
  96. package/src/services/mutating-approval.service.ts +1 -1
  97. package/src/services/node-workspace.service.ts +155 -0
  98. package/src/services/notification.service.ts +39 -0
  99. package/src/services/organization-member.service.ts +12 -5
  100. package/src/services/organization.service.ts +5 -5
  101. package/src/services/ownership-dispatcher.service.ts +403 -0
  102. package/src/services/plan-approval.service.ts +1 -1
  103. package/src/services/plan-artifact.service.ts +1 -0
  104. package/src/services/plan-builder.service.ts +1 -0
  105. package/src/services/plan-checkpoint.service.ts +30 -2
  106. package/src/services/plan-compiler.service.ts +5 -0
  107. package/src/services/plan-coordination.service.ts +152 -0
  108. package/src/services/plan-cycle.service.ts +284 -0
  109. package/src/services/plan-deadline.service.ts +287 -0
  110. package/src/services/plan-executor.service.ts +386 -58
  111. package/src/services/plan-helpers.ts +15 -0
  112. package/src/services/plan-run.service.ts +41 -7
  113. package/src/services/plan-scheduler.service.ts +240 -0
  114. package/src/services/plan-template.service.ts +117 -0
  115. package/src/services/plan-validator.service.ts +87 -20
  116. package/src/services/plan-workspace.service.ts +83 -0
  117. package/src/services/playbook-registry.service.ts +67 -0
  118. package/src/services/plugin-executor.service.ts +103 -0
  119. package/src/services/quality-metrics.service.ts +132 -0
  120. package/src/services/recent-activity-title.service.ts +3 -10
  121. package/src/services/recent-activity.service.ts +33 -43
  122. package/src/services/skill-resolver.service.ts +19 -0
  123. package/src/services/system-executor.service.ts +105 -0
  124. package/src/services/workstream-message.service.ts +29 -41
  125. package/src/services/workstream-plan-registry.service.ts +22 -0
  126. package/src/services/workstream-title.service.ts +3 -9
  127. package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
  128. package/src/services/workstream-turn.ts +2 -2
  129. package/src/services/workstream.service.ts +55 -65
  130. package/src/services/workstream.types.ts +10 -19
  131. package/src/services/write-intent-validator.service.ts +81 -0
  132. package/src/storage/attachment-parser.ts +1 -1
  133. package/src/storage/attachment-storage.service.ts +4 -4
  134. package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
  135. package/src/storage/generated-document-storage.service.ts +3 -2
  136. package/src/storage/index.ts +2 -2
  137. package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
  138. package/src/system-agents/delegated-agent-factory.ts +5 -2
  139. package/src/system-agents/index.ts +8 -0
  140. package/src/system-agents/memory-reranker.agent.ts +1 -1
  141. package/src/system-agents/memory.agent.ts +1 -1
  142. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  143. package/src/tools/execution-plan.tool.ts +17 -19
  144. package/src/tools/fetch-webpage.tool.ts +20 -18
  145. package/src/tools/index.ts +2 -3
  146. package/src/tools/read-file-parts.tool.ts +1 -1
  147. package/src/tools/search-web.tool.ts +18 -15
  148. package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
  149. package/src/tools/team-think.tool.ts +14 -8
  150. package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
  151. package/src/utils/async.ts +3 -2
  152. package/src/utils/date-time.ts +4 -32
  153. package/src/utils/env.ts +8 -0
  154. package/src/utils/errors.ts +47 -0
  155. package/src/utils/hono-error-handler.ts +1 -2
  156. package/src/utils/index.ts +19 -2
  157. package/src/utils/string.ts +128 -1
  158. package/src/workers/bootstrap.ts +2 -2
  159. package/src/workers/index.ts +1 -0
  160. package/src/workers/memory-consolidation.worker.ts +12 -12
  161. package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
  162. package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
  163. package/src/workers/skill-extraction.runner.ts +8 -102
  164. package/src/workers/utils/file-section-chunker.ts +6 -3
  165. package/src/workers/utils/repomix-file-sections.ts +2 -2
  166. package/src/workers/utils/sandbox-error.ts +11 -2
  167. package/src/workers/utils/workstream-message-query.ts +97 -0
  168. package/src/workers/worker-utils.ts +6 -2
  169. package/src/runtime/retrieval-pipeline.ts +0 -3
  170. package/src/runtime.ts +0 -387
  171. package/src/tools/log-hello-world.tool.ts +0 -17
  172. package/src/utils/error.ts +0 -10
  173. /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
  174. /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
@@ -4,8 +4,8 @@ import { createUIMessageStream } from 'ai'
4
4
  import { lotaDebugLogger } from '../config/debug-logger'
5
5
  import { hasApprovalRespondedParts, isApprovalContinuationRequest } from '../runtime/approval-continuation'
6
6
  import { wrapResponseWithKeepalive } from '../utils/sse-keepalive'
7
- import { prepareWorkstreamRunCore } from './workstream-turn-preparation'
8
- import type { WorkstreamTurnParams, WorkstreamApprovalContinuationParams } from './workstream-turn-preparation'
7
+ import { prepareWorkstreamRunCore } from './workstream-turn-preparation.service'
8
+ import type { WorkstreamTurnParams, WorkstreamApprovalContinuationParams } from './workstream-turn-preparation.service'
9
9
 
10
10
  export { hasApprovalRespondedParts, isApprovalContinuationRequest }
11
11
  export { wrapResponseWithKeepalive }
@@ -1,7 +1,8 @@
1
- import { WORKSTREAM } from '@lota-sdk/shared'
1
+ import { WORKSTREAM, sdkWorkstreamStatusSchema } from '@lota-sdk/shared'
2
2
  import { BoundQuery, RecordId, StringRecordId, surql } from 'surrealdb'
3
3
 
4
4
  import { agentDisplayNames, getCoreWorkstreamProfile, isAgentName } from '../config/agent-defaults'
5
+ import { serverLogger } from '../config/logger'
5
6
  import { getWorkstreamBootstrapConfig } from '../config/workstream-defaults'
6
7
  import { BaseService } from '../db/base.service'
7
8
  import { ensureRecordId, recordIdToString } from '../db/record-id'
@@ -24,7 +25,7 @@ import { toIsoDateTimeString } from '../utils/date-time'
24
25
  import { chatRunRegistry } from './chat-run-registry.service'
25
26
  import { contextCompactionService } from './context-compaction.service'
26
27
  import { workstreamMessageService } from './workstream-message.service'
27
- import { WorkstreamSchema, WorkstreamStatusSchema } from './workstream.types'
28
+ import { WorkstreamSchema } from './workstream.types'
28
29
  import type { NormalizedWorkstream, WorkstreamRecord } from './workstream.types'
29
30
 
30
31
  // Uses SurrealQL directly to keep pagination/order logic close to queries.
@@ -98,7 +99,7 @@ function requireDirectAgentId(agentId: string | undefined): string {
98
99
  return agentId
99
100
  }
100
101
 
101
- function requirestring(coreType: string | undefined): string {
102
+ function requireString(coreType: string | undefined): string {
102
103
  if (!coreType) {
103
104
  throw new Error('Core workstreams require a coreType')
104
105
  }
@@ -173,7 +174,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
173
174
  return options.title
174
175
  }
175
176
  if (core) {
176
- return getCoreWorkstreamProfile(requirestring(coreType)).config.title
177
+ return getCoreWorkstreamProfile(requireString(coreType)).config.title
177
178
  }
178
179
  if (mode === 'direct') {
179
180
  return getAgentDisplayName(requireDirectAgentId(directAgentId))
@@ -219,7 +220,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
219
220
  }
220
221
 
221
222
  if (core) {
222
- const resolvedCoreType = requirestring(coreType)
223
+ const resolvedCoreType = requireString(coreType)
223
224
  const coreProfile = getCoreWorkstreamProfile(resolvedCoreType)
224
225
  const coreWorkstreamId = buildCoreWorkstreamId({ userId, orgId, coreType: resolvedCoreType })
225
226
  const existing = await this.findById(coreWorkstreamId)
@@ -272,6 +273,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
272
273
  core: false,
273
274
  title,
274
275
  status: 'regular',
276
+ nameGenerated: options?.title !== undefined && options.title !== WORKSTREAM.DEFAULT_TITLE,
275
277
  })
276
278
 
277
279
  return this.normalizeWorkstream(groupWorkstream)
@@ -293,7 +295,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
293
295
  )
294
296
 
295
297
  const hasStandardGroupWorkstream = existingWorkstreams.some(
296
- (workstream) => workstream.mode === 'group' && workstream.core !== true,
298
+ (workstream) => workstream.mode === 'group' && !workstream.core,
297
299
  )
298
300
  const directWorkstreamsByAgent = new Map<string, WorkstreamRecord>()
299
301
  const coreWorkstreamsByType = new Map<string, WorkstreamRecord>()
@@ -302,7 +304,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
302
304
  directWorkstreamsByAgent.set(workstream.agentId, workstream)
303
305
  }
304
306
  for (const workstream of existingWorkstreams) {
305
- if (workstream.mode !== 'group' || workstream.core !== true) continue
307
+ if (workstream.mode !== 'group' || !workstream.core) continue
306
308
  if (typeof workstream.coreType !== 'string') continue
307
309
  coreWorkstreamsByType.set(workstream.coreType, workstream)
308
310
  }
@@ -368,15 +370,16 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
368
370
  async listWorkstreams(
369
371
  userId: RecordIdRef,
370
372
  orgId: RecordIdRef,
371
- options: { mode: string; core?: boolean; take?: number; page?: number; includeArchived: boolean },
373
+ options: { mode: string; core?: boolean; take?: number; page?: number; includeArchived?: boolean },
372
374
  ): Promise<{ workstreams: NormalizedWorkstream[]; hasMore: boolean }> {
373
375
  const core = options.core === true
376
+ const includeArchived = options.includeArchived ?? false
374
377
  if (options.mode === 'direct' && core) {
375
378
  throw new Error('Direct workstreams cannot be queried as core workstreams')
376
379
  }
377
380
 
378
381
  if (options.mode === 'direct' || core) {
379
- const query = options.includeArchived
382
+ const query = includeArchived
380
383
  ? LIST_ALL_WORKSTREAMS_BY_MODE_QUERY
381
384
  : LIST_ALL_WORKSTREAMS_BY_MODE_EXCLUDE_ARCHIVED_QUERY
382
385
  const workstreams = await databaseService.queryMany<typeof WorkstreamSchema>(
@@ -389,7 +392,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
389
392
 
390
393
  const take = options.take ?? WORKSTREAM.DEFAULT_PAGE_LIMIT
391
394
  const page = options.page ?? 1
392
- const query = options.includeArchived ? LIST_WORKSTREAMS_QUERY : LIST_WORKSTREAMS_EXCLUDE_ARCHIVED_QUERY
395
+ const query = includeArchived ? LIST_WORKSTREAMS_QUERY : LIST_WORKSTREAMS_EXCLUDE_ARCHIVED_QUERY
393
396
  const workstreams = await databaseService.queryMany<typeof WorkstreamSchema>(
394
397
  new BoundQuery(query, {
395
398
  userId,
@@ -455,19 +458,15 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
455
458
  return this.normalizeWorkstream(workstream)
456
459
  }
457
460
 
458
- async getWorkstreamRecord(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
459
- return await this.getById(workstreamId)
460
- }
461
-
462
461
  async updateTitle(workstreamId: RecordIdRef, title: string): Promise<NormalizedWorkstream> {
463
462
  const existing = await this.getById(workstreamId)
464
463
  this.assertMutableWorkstream(existing, 'rename')
465
- const workstream = await this.update(workstreamId, { title })
464
+ const workstream = await this.update(workstreamId, { title, nameGenerated: true })
466
465
  return this.normalizeWorkstream(workstream)
467
466
  }
468
467
 
469
468
  async updateStatus(workstreamId: RecordIdRef, status: string): Promise<NormalizedWorkstream> {
470
- const validStatus = WorkstreamStatusSchema.parse(status)
469
+ const validStatus = sdkWorkstreamStatusSchema.parse(status)
471
470
  const existing = await this.getById(workstreamId)
472
471
  this.assertMutableWorkstream(existing, validStatus === 'archived' ? 'archive' : 'unarchive')
473
472
  const workstream = await this.update(workstreamId, { status: validStatus })
@@ -499,9 +498,8 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
499
498
  }
500
499
 
501
500
  async clearActiveRunIdIfMatches(workstreamId: RecordIdRef, runId: string): Promise<void> {
502
- const activeRunId = await this.getActiveRunId(workstreamId)
503
- if (activeRunId !== runId) return
504
- await this.setActiveRunId(workstreamId, null)
501
+ const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
502
+ await databaseService.query(surql`UPDATE ONLY ${workstreamRef} SET activeRunId = NONE WHERE activeRunId = ${runId}`)
505
503
  }
506
504
 
507
505
  async setActiveStreamId(workstreamId: RecordIdRef, streamId: string): Promise<void> {
@@ -521,13 +519,10 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
521
519
  }
522
520
 
523
521
  async clearActiveStreamIdIfMatches(workstreamId: RecordIdRef, streamId: string): Promise<void> {
524
- const activeStreamId = await this.getActiveStreamId(workstreamId)
525
- if (activeStreamId !== streamId) return
526
522
  const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
527
- await databaseService.query<unknown>(surql`
528
- UPDATE ONLY ${workstreamRef}
529
- SET activeStreamId = NONE
530
- `)
523
+ await databaseService.query(
524
+ surql`UPDATE ONLY ${workstreamRef} SET activeStreamId = NONE WHERE activeStreamId = ${streamId}`,
525
+ )
531
526
  }
532
527
 
533
528
  async stopActiveRun(workstreamId: RecordIdRef): Promise<boolean> {
@@ -543,11 +538,19 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
543
538
  return false
544
539
  }
545
540
 
546
- async setCompacting(workstreamId: RecordIdRef, value: boolean): Promise<void> {
541
+ async markCompacting(workstreamId: RecordIdRef): Promise<void> {
542
+ const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
543
+ await databaseService.query<unknown>(surql`
544
+ UPDATE ONLY ${workstreamRef}
545
+ SET isCompacting = ${true}
546
+ `)
547
+ }
548
+
549
+ async clearCompacting(workstreamId: RecordIdRef): Promise<void> {
547
550
  const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
548
551
  await databaseService.query<unknown>(surql`
549
552
  UPDATE ONLY ${workstreamRef}
550
- SET isCompacting = ${value}
553
+ SET isCompacting = ${false}
551
554
  `)
552
555
  }
553
556
 
@@ -566,7 +569,9 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
566
569
  await this.update(workstreamRef, { memoryBlock: serialized })
567
570
 
568
571
  if (updatedEntries.length >= MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES) {
569
- void this.compactMemoryBlock(workstreamRef).catch(() => {})
572
+ void this.compactMemoryBlock(workstreamRef).catch((err: unknown) => {
573
+ serverLogger.warn`Memory block compaction failed for ${workstreamRef}: ${err}`
574
+ })
570
575
  }
571
576
 
572
577
  return this.formatMemoryBlockForPrompt({
@@ -612,7 +617,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
612
617
  orgId: RecordIdRef
613
618
  excludeWorkstreamId?: RecordIdRef
614
619
  limit: number
615
- }) {
620
+ }): Promise<NormalizedWorkstream[]> {
616
621
  let excludeCondition = ''
617
622
  const vars: Record<string, unknown> = { userId, orgId, limit }
618
623
 
@@ -647,7 +652,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
647
652
  throw new Error(`Invalid record id for table ${table}`)
648
653
  }
649
654
 
650
- return recordIdToString(id, String(table))
655
+ return recordIdToString(id, table)
651
656
  }
652
657
 
653
658
  formatMemoryBlockForPrompt(workstream: Pick<WorkstreamRecord, 'memoryBlock' | 'memoryBlockSummary'>): string {
@@ -658,7 +663,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
658
663
  }
659
664
 
660
665
  private getDefaultTitle(workstream: Pick<WorkstreamRecord, 'core' | 'coreType'>): string {
661
- if (workstream.core === true && typeof workstream.coreType === 'string') {
666
+ if (workstream.core && typeof workstream.coreType === 'string') {
662
667
  return getCoreWorkstreamProfile(workstream.coreType).config.title
663
668
  }
664
669
 
@@ -671,10 +676,10 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
671
676
  ? workstream.activeRunId
672
677
  : null
673
678
  const isCompacting = workstream.isCompacting === true
674
- const mode = typeof workstream.mode === 'string' ? workstream.mode : 'group'
675
- const core = workstream.core === true
679
+ const mode = workstream.mode
680
+ const core = workstream.core
676
681
  const coreType = core && typeof workstream.coreType === 'string' ? workstream.coreType : undefined
677
- const status = typeof workstream.status === 'string' ? workstream.status : 'regular'
682
+ const status = workstream.status
678
683
  return {
679
684
  id: this.normalizeWorkstreamId(workstream.id),
680
685
  userId: this.normalizeRecordIdString(workstream.userId, TABLES.USER),
@@ -682,7 +687,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
682
687
  mode,
683
688
  core,
684
689
  ...(coreType ? { coreType } : {}),
685
- nameGenerated: workstream.nameGenerated === true,
690
+ nameGenerated: workstream.nameGenerated,
686
691
  isRunning: activeRunId !== null,
687
692
  isCompacting,
688
693
  ...(isAgentName(workstream.agentId) ? { agentId: workstream.agentId } : {}),
@@ -694,35 +699,20 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
694
699
  }
695
700
  }
696
701
 
697
- toPublicWorkstream(workstream: NormalizedWorkstream | WorkstreamRecord) {
698
- const id = typeof workstream.id === 'string' ? workstream.id : this.normalizeWorkstreamId(workstream.id)
699
- const createdAt = toIsoDateTimeString(workstream.createdAt)
700
- const updatedAt = toIsoDateTimeString(workstream.updatedAt)
701
- const activeRunId =
702
- 'activeRunId' in workstream &&
703
- typeof workstream.activeRunId === 'string' &&
704
- workstream.activeRunId.trim().length > 0
705
- ? workstream.activeRunId
706
- : null
707
- const isRunning = 'isRunning' in workstream ? workstream.isRunning : activeRunId !== null
708
- const isCompacting = workstream.isCompacting === true
709
- const mode = typeof workstream.mode === 'string' ? workstream.mode : 'group'
710
- const core = workstream.core === true
711
- const coreType = core && typeof workstream.coreType === 'string' ? workstream.coreType : undefined
712
- const nameGenerated = 'nameGenerated' in workstream ? workstream.nameGenerated === true : false
702
+ toPublicWorkstream(workstream: NormalizedWorkstream) {
713
703
  return {
714
- id,
715
- mode,
716
- core,
717
- ...(coreType ? { coreType } : {}),
718
- ...(isAgentName(workstream.agentId) ? { agentId: workstream.agentId } : {}),
719
- title: workstream.title ?? this.getDefaultTitle(workstream),
720
- status: workstream.status ?? 'regular',
721
- nameGenerated,
722
- isRunning,
723
- isCompacting,
724
- createdAt,
725
- updatedAt,
704
+ id: workstream.id,
705
+ mode: workstream.mode,
706
+ core: workstream.core,
707
+ ...(workstream.coreType ? { coreType: workstream.coreType } : {}),
708
+ ...(workstream.agentId ? { agentId: workstream.agentId } : {}),
709
+ title: workstream.title,
710
+ status: workstream.status,
711
+ nameGenerated: workstream.nameGenerated,
712
+ isRunning: workstream.isRunning,
713
+ isCompacting: workstream.isCompacting,
714
+ createdAt: workstream.createdAt,
715
+ updatedAt: workstream.updatedAt,
726
716
  }
727
717
  }
728
718
 
@@ -733,7 +723,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
733
723
  SET turnCount += 1
734
724
  RETURN turnCount
735
725
  `)
736
- return result[0]?.turnCount ?? 0
726
+ return result[0].turnCount
737
727
  }
738
728
 
739
729
  async persistGeneratedTitle(workstreamId: RecordIdRef, title: string): Promise<void> {
@@ -747,7 +737,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
747
737
  if (workstream.mode === 'direct') {
748
738
  throw new Error(`Direct workstreams cannot be ${action}d`)
749
739
  }
750
- if (workstream.core === true) {
740
+ if (workstream.core) {
751
741
  throw new Error(`Core workstreams cannot be ${action}d`)
752
742
  }
753
743
  }
@@ -1,16 +1,7 @@
1
+ import { recordIdSchema, sdkWorkstreamStatusSchema } from '@lota-sdk/shared'
1
2
  import { z } from 'zod'
2
3
 
3
- export interface Citation {
4
- title?: string
5
- url?: string
6
- snippet?: string
7
- source?: string
8
- sourceId?: string
9
- retrievedAt?: string
10
- }
11
-
12
4
  const WorkstreamModeSchema = z.enum(['direct', 'group'])
13
- export const WorkstreamStatusSchema = z.enum(['regular', 'archived'])
14
5
  const CoreWorkstreamTypeSchema = z.string()
15
6
 
16
7
  export interface NormalizedWorkstream {
@@ -20,7 +11,7 @@ export interface NormalizedWorkstream {
20
11
  mode: 'direct' | 'group'
21
12
  core: boolean
22
13
  coreType?: string
23
- nameGenerated: boolean
14
+ nameGenerated: boolean // Ideally `isNameGenerated`, but maps directly to SurrealDB column `nameGenerated`
24
15
  isRunning: boolean
25
16
  isCompacting: boolean
26
17
  agentId?: string | null
@@ -32,27 +23,27 @@ export interface NormalizedWorkstream {
32
23
  }
33
24
 
34
25
  export const WorkstreamSchema = z.object({
35
- id: z.any(), // RecordId
36
- mode: WorkstreamModeSchema.optional().default('group'),
37
- core: z.boolean().optional().default(false),
26
+ id: recordIdSchema,
27
+ mode: WorkstreamModeSchema,
28
+ core: z.boolean(),
38
29
  coreType: CoreWorkstreamTypeSchema.nullish(),
39
30
  agentId: z.string().nullish(),
40
31
  title: z.string().nullish(),
41
- status: WorkstreamStatusSchema.nullish(),
32
+ status: sdkWorkstreamStatusSchema,
42
33
  memoryBlock: z.string().nullish(),
43
34
  memoryBlockSummary: z.string().nullish(),
44
35
  activeRunId: z.string().nullish(),
45
36
  activeStreamId: z.string().nullish(),
46
37
  compactionSummary: z.string().nullish(),
47
38
  lastCompactedMessageId: z.string().nullish(),
48
- nameGenerated: z.boolean().optional().default(false),
39
+ nameGenerated: z.boolean(), // Ideally `isNameGenerated`, but maps directly to SurrealDB column `nameGenerated`
49
40
  isCompacting: z.boolean().optional(),
50
41
  state: z.unknown().optional(),
51
- turnCount: z.number().int().optional().default(0),
42
+ turnCount: z.number().int(),
52
43
  createdAt: z.coerce.date(),
53
44
  updatedAt: z.coerce.date(),
54
- userId: z.any(), // RecordId
55
- organizationId: z.any(), // RecordId
45
+ userId: recordIdSchema,
46
+ organizationId: recordIdSchema,
56
47
  })
57
48
 
58
49
  export type WorkstreamRecord = z.infer<typeof WorkstreamSchema>
@@ -0,0 +1,81 @@
1
+ import type { PlanDataSchema, PlanNodeSpec, PlanSchemaRegistry, WriteIntent } from '@lota-sdk/shared'
2
+
3
+ import { validateSchemaValue } from './plan-validator.service'
4
+
5
+ export interface WriteValidationIssue {
6
+ code: string
7
+ message: string
8
+ path?: string
9
+ }
10
+
11
+ export interface WriteValidationResult {
12
+ status: 'pass' | 'fail'
13
+ issues: WriteValidationIssue[]
14
+ suggestion?: string
15
+ validatedAt: string
16
+ }
17
+
18
+ class WriteIntentValidatorService {
19
+ validate(params: {
20
+ intent: WriteIntent
21
+ nodeSpec: PlanNodeSpec
22
+ schemaRegistry: PlanSchemaRegistry
23
+ existingDeliverables: Map<string, unknown>
24
+ }): WriteValidationResult {
25
+ const issues: WriteValidationIssue[] = []
26
+ const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
27
+
28
+ if (intent.targetPath.startsWith('structuredOutput')) {
29
+ if (nodeSpec.outputSchemaRef) {
30
+ const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
31
+ if (schema) {
32
+ const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
33
+ for (const message of schemaIssues) {
34
+ issues.push({ code: 'schema_validation_failed', message })
35
+ }
36
+ }
37
+ }
38
+ return this.buildResult(issues)
39
+ }
40
+
41
+ const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
42
+ if (!deliverable) {
43
+ issues.push({
44
+ code: 'unknown_deliverable',
45
+ message: `"${intent.targetPath}" does not match any declared deliverable.`,
46
+ })
47
+ return this.buildResult(issues)
48
+ }
49
+
50
+ if (deliverable.schemaRef) {
51
+ const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
52
+ if (schema) {
53
+ const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
54
+ for (const message of schemaIssues) {
55
+ issues.push({ code: 'schema_validation_failed', message })
56
+ }
57
+ }
58
+ }
59
+
60
+ if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
61
+ issues.push({
62
+ code: 'update_target_not_found',
63
+ message: `Cannot update "${intent.targetPath}" — no prior write exists.`,
64
+ })
65
+ }
66
+
67
+ return this.buildResult(issues)
68
+ }
69
+
70
+ private buildResult(issues: WriteValidationIssue[]): WriteValidationResult {
71
+ const hasFailed = issues.length > 0
72
+ return {
73
+ status: hasFailed ? 'fail' : 'pass',
74
+ issues,
75
+ ...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
76
+ validatedAt: new Date().toISOString(),
77
+ }
78
+ }
79
+ }
80
+
81
+ export const writeIntentValidatorService = new WriteIntentValidatorService()
@@ -64,7 +64,7 @@ export async function extractAttachmentText(file: File): Promise<string> {
64
64
  return normalizeExtractedText((await extractPdfPages(file)).join('\n\n'))
65
65
  }
66
66
  if (isDocxAttachmentFile(file)) {
67
- return await extractDocxText(file)
67
+ return extractDocxText(file)
68
68
  }
69
69
  return ''
70
70
  }
@@ -15,14 +15,14 @@ import type {
15
15
  ReadableUploadMetadata,
16
16
  ReadableUploadPageMode,
17
17
  ReadableUploadPagePart,
18
- } from './attachments.types'
18
+ } from './attachment-types'
19
19
  import {
20
20
  buildOrganizationDocumentStorageKey,
21
21
  buildUploadStorageKey,
22
22
  buildUploadStoragePrefix,
23
23
  readNonNegativeInteger,
24
24
  readRecord,
25
- } from './attachments.utils'
25
+ } from './attachment-utils'
26
26
 
27
27
  const READ_FILE_PARTS_PAGES_PER_PART = 25
28
28
 
@@ -386,7 +386,7 @@ export function getAttachmentStorageService(): AttachmentStorageService {
386
386
  }
387
387
 
388
388
  export const attachmentStorageService = new Proxy({} as AttachmentStorageService, {
389
- get(_target, prop: string) {
390
- return (getAttachmentStorageService() as unknown as Record<string, unknown>)[prop]
389
+ get(_target, prop: string): unknown {
390
+ return Reflect.get(getAttachmentStorageService(), prop)
391
391
  },
392
392
  })
@@ -3,7 +3,7 @@ function sanitizeFilename(name: string): string {
3
3
  return ascii.replace(/[\\/:"*?<>|]+/g, '-').trim()
4
4
  }
5
5
 
6
- function toSafeSegment(value: string): string {
6
+ export function toSafeSegment(value: string): string {
7
7
  const cleaned = value.replace(/[^a-zA-Z0-9._-]/g, '-')
8
8
  return cleaned.length > 0 ? cleaned : 'unknown'
9
9
  }
@@ -48,10 +48,7 @@ export function buildOrganizationDocumentStorageKey(params: {
48
48
  return `${buildOrganizationDocumentStoragePrefix({ orgId: params.orgId, namespace: params.namespace })}${relativePath || 'document.txt'}`
49
49
  }
50
50
 
51
- export function readRecord(value: unknown): Record<string, unknown> | null {
52
- if (!value || typeof value !== 'object' || Array.isArray(value)) return null
53
- return value as Record<string, unknown>
54
- }
51
+ export { readRecord } from '../utils/string'
55
52
 
56
53
  export function readNonNegativeInteger(value: unknown): number | null {
57
54
  return Number.isInteger(value) && typeof value === 'number' && value >= 0 ? value : null
@@ -1,8 +1,9 @@
1
1
  import { S3Client } from 'bun'
2
2
 
3
3
  import { getRuntimeConfig } from '../runtime/runtime-config'
4
+ import { toSafeSegment } from './attachment-utils'
4
5
 
5
- function toSafeSegment(value: string): string {
6
+ function toSafePathSegment(value: string): string {
6
7
  return value
7
8
  .trim()
8
9
  .replace(/\\/g, '/')
@@ -18,7 +19,7 @@ function buildGeneratedDocumentStorageKey(params: {
18
19
  }): string {
19
20
  const safeOrganizationId = toSafeSegment(params.organizationId)
20
21
  const safeNamespace = toSafeSegment(params.namespace)
21
- const safeRelativePath = toSafeSegment(params.relativePath)
22
+ const safeRelativePath = toSafePathSegment(params.relativePath)
22
23
  return `${safeOrganizationId}/generated/${safeNamespace}/${safeRelativePath}`
23
24
  }
24
25
 
@@ -1,10 +1,10 @@
1
1
  export * from './attachment-parser'
2
2
  export * from './attachment-storage.service'
3
3
  export * from './generated-document-storage.service'
4
- export type { MessagePartLike, ReadableUploadPageMode, ReadableUploadPagePart } from './attachments.types'
4
+ export type { MessagePartLike, ReadableUploadPageMode, ReadableUploadPagePart } from './attachment-types'
5
5
  export {
6
6
  buildOrganizationDocumentStorageKey,
7
7
  buildUploadStorageKey,
8
8
  buildUploadStoragePrefix,
9
9
  readNonNegativeInteger,
10
- } from './attachments.utils'
10
+ } from './attachment-utils'
@@ -8,7 +8,7 @@ import {
8
8
  import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
9
9
  import { resolveHelperAgentOptions } from './helper-agent-options'
10
10
 
11
- const contextCompacterPrompt = `<agent-instructions>
11
+ const CONTEXT_COMPACTION_PROMPT = `<agent-instructions>
12
12
  You are a **Context Compacter** that produces both:
13
13
  1) a dense but shorter summary of prior context
14
14
  2) a structured state delta for durable execution continuity
@@ -36,11 +36,11 @@ Return valid data for:
36
36
  </output-format>
37
37
  </agent-instructions>`
38
38
 
39
- export function createContextCompacterAgent(options: CreateHelperToolLoopAgentOptions) {
39
+ export function createContextCompactionAgent(options: CreateHelperToolLoopAgentOptions) {
40
40
  return new ToolLoopAgent({
41
- id: 'context-compacter',
41
+ id: 'context-compaction',
42
42
  model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
43
43
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
44
- ...resolveHelperAgentOptions(options, { instructions: contextCompacterPrompt }),
44
+ ...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
45
45
  })
46
46
  }
@@ -3,6 +3,7 @@ import type { ModelMessage, LanguageModel, ToolLoopAgentSettings, ToolSet } from
3
3
  import { z } from 'zod'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
6
+ import { aiLogger } from '../config/logger'
6
7
  import { isRecord } from '../utils/string'
7
8
  import { assertSubstantiveAgentResult } from './agent-result'
8
9
 
@@ -79,6 +80,7 @@ const TERMINATION_SUFFIX = `
79
80
  When your analysis is complete, return your final answer directly as markdown text. Do not leave the task unfinished or end on a tool result without a final response.
80
81
  </termination>`
81
82
 
83
+ const DEFAULT_DELEGATED_AGENT_MAX_OUTPUT_TOKENS = 4096
82
84
  const MAX_RETAINED_AGENT_MESSAGES = 10
83
85
  const MAX_NON_SUBSTANTIVE_AGENT_RESULT_ATTEMPTS = 2
84
86
  const NON_SUBSTANTIVE_AGENT_RESULT_RETRY_PROMPT =
@@ -135,6 +137,7 @@ async function generateSubstantiveDelegatedAgentResult(params: {
135
137
  try {
136
138
  return assertSubstantiveAgentResult(result.text, params.label)
137
139
  } catch (error) {
140
+ aiLogger.error`Delegated agent returned non-substantive result (label=${params.label}, attempt=${attempt + 1}, textLength=${result.text.length}, textPreview=${result.text.slice(0, 200)})`
138
141
  lastError = error
139
142
  if (params.abortSignal?.aborted) {
140
143
  throw error
@@ -164,7 +167,7 @@ export function createDelegatedAgentTool(definition: DelegatedAgentDefinition):
164
167
  ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
165
168
  instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
166
169
  tools: agentTools,
167
- maxOutputTokens: definition.maxOutputTokens ?? 4096,
170
+ maxOutputTokens: definition.maxOutputTokens ?? DEFAULT_DELEGATED_AGENT_MAX_OUTPUT_TOKENS,
168
171
  ...(typeof temperature === 'number' ? { temperature } : {}),
169
172
  stopWhen: [stepCountIs(maxSteps)],
170
173
  prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
@@ -205,7 +208,7 @@ export function createDelegatedAgentToolWithContext<TContext>(
205
208
  ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
206
209
  instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
207
210
  tools: agentTools,
208
- maxOutputTokens: definition.maxOutputTokens ?? 4096,
211
+ maxOutputTokens: definition.maxOutputTokens ?? DEFAULT_DELEGATED_AGENT_MAX_OUTPUT_TOKENS,
209
212
  ...(typeof temperature === 'number' ? { temperature } : {}),
210
213
  stopWhen: [stepCountIs(maxSteps)],
211
214
  prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
@@ -1,4 +1,12 @@
1
1
  export * from './agent-result'
2
+ export * from './context-compaction.agent'
2
3
  export * from './delegated-agent-factory'
3
4
  export * from './helper-agent-options'
5
+ export * from './memory-reranker.agent'
6
+ export * from './memory.agent'
4
7
  export * from './recent-activity-title-refiner.agent'
8
+ export * from './regular-chat-memory-digest.agent'
9
+ export * from './researcher.agent'
10
+ export * from './skill-extractor.agent'
11
+ export * from './skill-manager.agent'
12
+ export * from './title-generator.agent'
@@ -8,7 +8,7 @@ import {
8
8
  import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
9
9
  import { resolveHelperAgentOptions } from './helper-agent-options'
10
10
 
11
- export const memoryRerankerPrompt = `<agent-instructions>
11
+ export const MEMORY_RERANKER_PROMPT = `<agent-instructions>
12
12
  You are a **Memory Reranker** that selects and organizes the most relevant memories for a user query.
13
13
 
14
14
  <task>
@@ -8,7 +8,7 @@ import {
8
8
  import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
9
9
  import { resolveHelperAgentOptions } from './helper-agent-options'
10
10
 
11
- export const orgMemoryPrompt = `<agent-instructions>
11
+ export const ORG_MEMORY_PROMPT = `<agent-instructions>
12
12
  You are an **Organization Fact Extractor** that captures only durable, explicitly stated organization facts.
13
13
 
14
14
  <task>
@@ -45,7 +45,7 @@ Return only the title text. No quotes, labels, JSON, markdown, or explanation.
45
45
  </agent-instructions>`
46
46
  }
47
47
 
48
- export const recentActivityTitleRefinerPrompt = `<agent-instructions>
48
+ export const RECENT_ACTIVITY_TITLE_REFINER_PROMPT = `<agent-instructions>
49
49
  You are the lead agent writing the visible title for a recent activity item.
50
50
 
51
51
  <goal>