@lota-sdk/core 0.1.23 → 0.1.25

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 (78) hide show
  1. package/package.json +2 -2
  2. package/src/ai/definitions.ts +5 -59
  3. package/src/ai-gateway/ai-gateway.ts +36 -28
  4. package/src/ai-gateway/cache-headers.ts +9 -0
  5. package/src/config/model-constants.ts +6 -2
  6. package/src/create-runtime.ts +5 -17
  7. package/src/db/memory-types.ts +13 -8
  8. package/src/db/memory.ts +74 -53
  9. package/src/queues/autonomous-job.queue.ts +1 -8
  10. package/src/queues/context-compaction.queue.ts +2 -2
  11. package/src/queues/index.ts +2 -6
  12. package/src/queues/organization-learning.queue.ts +78 -0
  13. package/src/queues/plan-agent-heartbeat.queue.ts +10 -16
  14. package/src/queues/title-generation.queue.ts +62 -0
  15. package/src/runtime/agent-prompt-context.ts +0 -18
  16. package/src/runtime/agent-runtime-policy.ts +9 -2
  17. package/src/runtime/context-compaction-constants.ts +4 -2
  18. package/src/runtime/context-compaction.ts +135 -118
  19. package/src/runtime/execution-plan.ts +2 -1
  20. package/src/runtime/memory-pipeline.ts +70 -1
  21. package/src/runtime/memory-prompts-fact.ts +16 -0
  22. package/src/runtime/plugin-resolution.ts +3 -2
  23. package/src/runtime/plugin-types.ts +1 -42
  24. package/src/runtime/post-turn-side-effects.ts +212 -0
  25. package/src/runtime/runtime-config.ts +0 -13
  26. package/src/runtime/runtime-extensions.ts +10 -16
  27. package/src/runtime/runtime-worker-registry.ts +8 -19
  28. package/src/runtime/social-chat-agent-runner.ts +119 -0
  29. package/src/runtime/social-chat-history.ts +110 -0
  30. package/src/runtime/social-chat-prompts.ts +58 -0
  31. package/src/runtime/social-chat.ts +104 -340
  32. package/src/runtime/specialist-runner.ts +18 -0
  33. package/src/runtime/workstream-chat-helpers.ts +19 -0
  34. package/src/runtime/workstream-plan-turn.ts +195 -0
  35. package/src/runtime/workstream-state.ts +11 -8
  36. package/src/runtime/workstream-turn-context.ts +183 -0
  37. package/src/services/agent-activity.service.ts +350 -0
  38. package/src/services/autonomous-job.service.ts +1 -8
  39. package/src/services/execution-plan.service.ts +205 -334
  40. package/src/services/index.ts +2 -4
  41. package/src/services/memory.service.ts +54 -44
  42. package/src/services/ownership-dispatcher.service.ts +2 -19
  43. package/src/services/plan-completion-side-effects.ts +80 -0
  44. package/src/services/plan-event-delivery.service.ts +1 -1
  45. package/src/services/plan-executor.service.ts +42 -190
  46. package/src/services/plan-node-spec.ts +60 -0
  47. package/src/services/plan-run-data.ts +88 -0
  48. package/src/services/plan-validator.service.ts +10 -8
  49. package/src/services/workstream-constants.ts +2 -0
  50. package/src/services/workstream-title.service.ts +1 -1
  51. package/src/services/workstream-turn-preparation.service.ts +208 -715
  52. package/src/services/workstream.service.ts +162 -192
  53. package/src/services/workstream.types.ts +12 -44
  54. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -0
  55. package/src/tools/execution-plan.tool.ts +11 -6
  56. package/src/tools/index.ts +1 -0
  57. package/src/tools/project-with-plan.tool.ts +87 -0
  58. package/src/tools/remember-memory.tool.ts +7 -10
  59. package/src/tools/research-topic.tool.ts +1 -1
  60. package/src/tools/team-think.tool.ts +1 -1
  61. package/src/tools/user-questions.tool.ts +1 -1
  62. package/src/utils/autonomous-job-ids.ts +7 -0
  63. package/src/workers/organization-learning.worker.ts +31 -0
  64. package/src/workers/regular-chat-memory-digest.runner.ts +9 -3
  65. package/src/workers/skill-extraction.runner.ts +2 -2
  66. package/src/queues/recent-activity-title-refinement.queue.ts +0 -30
  67. package/src/queues/regular-chat-memory-digest.config.ts +0 -12
  68. package/src/queues/regular-chat-memory-digest.queue.ts +0 -34
  69. package/src/queues/skill-extraction.config.ts +0 -9
  70. package/src/queues/skill-extraction.queue.ts +0 -27
  71. package/src/queues/workstream-title-generation.queue.ts +0 -33
  72. package/src/services/context-enrichment.service.ts +0 -33
  73. package/src/services/coordination-registry.service.ts +0 -117
  74. package/src/services/domain-agent-executor.service.ts +0 -71
  75. package/src/services/memory-assessment.service.ts +0 -44
  76. package/src/services/playbook-registry.service.ts +0 -67
  77. package/src/workers/regular-chat-memory-digest.worker.ts +0 -22
  78. package/src/workers/skill-extraction.worker.ts +0 -22
@@ -9,6 +9,7 @@ const SCORE_WEIGHTS = {
9
9
  interface MemoryFactInput {
10
10
  content: string
11
11
  confidence: number
12
+ importance?: number
12
13
  durability?: string
13
14
  type?: string
14
15
  }
@@ -23,6 +24,7 @@ interface MemoryImportanceParams {
23
24
  text: string
24
25
  memoryType: string
25
26
  explicitImportance?: number
27
+ extractedImportanceByKey: Map<string, number>
26
28
  confidenceByKey: Map<string, number>
27
29
  durabilityByKey: Map<string, string>
28
30
  categoryByKey: Map<string, string | undefined>
@@ -30,6 +32,7 @@ interface MemoryImportanceParams {
30
32
  }
31
33
 
32
34
  interface MemoryFactMaps {
35
+ extractedImportanceByKey: Map<string, number>
33
36
  confidenceByKey: Map<string, number>
34
37
  durabilityByKey: Map<string, string>
35
38
  categoryByKey: Map<string, string | undefined>
@@ -195,18 +198,26 @@ export function postProcessMemoryFacts<T extends MemoryFactInput>(
195
198
  }
196
199
 
197
200
  export function buildMemoryFactMaps<T extends MemoryFactInput>(facts: T[]): MemoryFactMaps {
201
+ const extractedImportanceByKey = new Map<string, number>()
198
202
  const confidenceByKey = new Map<string, number>()
199
203
  const durabilityByKey = new Map<string, string>()
200
204
  const categoryByKey = new Map<string, string | undefined>()
201
205
 
202
206
  for (const fact of facts) {
203
207
  const key = normalizeMemoryKey(fact.content)
208
+ const extractedImportance =
209
+ typeof (fact as T & { importance?: number }).importance === 'number'
210
+ ? clampImportance((fact as T & { importance: number }).importance)
211
+ : undefined
212
+ if (extractedImportance !== undefined) {
213
+ extractedImportanceByKey.set(key, extractedImportance)
214
+ }
204
215
  confidenceByKey.set(key, fact.confidence)
205
216
  durabilityByKey.set(key, fact.durability ?? 'standard')
206
217
  categoryByKey.set(key, fact.type)
207
218
  }
208
219
 
209
- return { confidenceByKey, durabilityByKey, categoryByKey }
220
+ return { extractedImportanceByKey, confidenceByKey, durabilityByKey, categoryByKey }
210
221
  }
211
222
 
212
223
  function toUniqueOrderedIds(value: string[] | undefined, validIds: Set<string>): string[] {
@@ -402,6 +413,56 @@ export function compileMemoryUpdatesFromDelta(params: {
402
413
  return { memory: updates }
403
414
  }
404
415
 
416
+ export function projectMemoryDeltaToScope(params: {
417
+ delta: MemoryDeltaOutputLike
418
+ scopeMemoryIds: readonly string[]
419
+ scopeMemoryIdsByUnionId?: Readonly<Record<string, readonly string[]>>
420
+ }): MemoryDeltaOutputLike {
421
+ const validExistingIds = new Set(params.scopeMemoryIds)
422
+ const mapTargetMemoryIds = (targetIds: readonly string[] | undefined): string[] => {
423
+ if (!targetIds || targetIds.length === 0) return []
424
+
425
+ const mappedIds = targetIds.flatMap((targetId) => {
426
+ const directId = targetId.trim()
427
+ if (!directId) return []
428
+
429
+ const aliasedIds = params.scopeMemoryIdsByUnionId?.[directId]
430
+ if (aliasedIds && aliasedIds.length > 0) {
431
+ return aliasedIds.filter((id) => validExistingIds.has(id))
432
+ }
433
+
434
+ return validExistingIds.has(directId) ? [directId] : []
435
+ })
436
+
437
+ return [...new Set(mappedIds)]
438
+ }
439
+
440
+ return {
441
+ deltas: params.delta.deltas.map((delta) => {
442
+ const targetMemoryIds = mapTargetMemoryIds(delta.targetMemoryIds)
443
+ const invalidateTargetIds = mapTargetMemoryIds(delta.invalidateTargetIds).filter((id) =>
444
+ targetMemoryIds.includes(id),
445
+ )
446
+ const relations = (delta.relations ?? []).flatMap((relation) => {
447
+ if (typeof relation.targetFactIndex === 'number') return [relation]
448
+ if (typeof relation.targetMemoryId !== 'string') return []
449
+
450
+ return mapTargetMemoryIds([relation.targetMemoryId]).map((targetMemoryId) => ({ ...relation, targetMemoryId }))
451
+ })
452
+ const hasScopeTarget = targetMemoryIds.length > 0
453
+
454
+ return {
455
+ fact: delta.fact,
456
+ classification: hasScopeTarget || delta.classification === 'new' ? delta.classification : 'new',
457
+ targetMemoryIds,
458
+ invalidateTargetIds,
459
+ relations,
460
+ rationale: delta.rationale ?? 'Projected onto the current memory scope.',
461
+ }
462
+ }),
463
+ }
464
+ }
465
+
405
466
  function resolveMemoryImportance(params: MemoryImportanceParams): number {
406
467
  if (typeof params.explicitImportance === 'number') {
407
468
  return clampImportance(params.explicitImportance)
@@ -412,6 +473,11 @@ function resolveMemoryImportance(params: MemoryImportanceParams): number {
412
473
  }
413
474
 
414
475
  const normalizedText = normalizeMemoryKey(params.text)
476
+ const extractedImportance = params.extractedImportanceByKey.get(normalizedText)
477
+ if (extractedImportance !== undefined) {
478
+ return clampImportance(extractedImportance)
479
+ }
480
+
415
481
  const aiConfidence = params.confidenceByKey.get(normalizedText)
416
482
  if (aiConfidence !== undefined) {
417
483
  const durability = params.durabilityByKey.get(normalizedText) ?? 'standard'
@@ -429,6 +495,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
429
495
  updates: MemoryUpdateOutputLike<TRelation>
430
496
  memoryType: string
431
497
  explicitImportance?: number
498
+ extractedImportanceByKey: Map<string, number>
432
499
  confidenceByKey: Map<string, number>
433
500
  durabilityByKey: Map<string, string>
434
501
  categoryByKey: Map<string, string | undefined>
@@ -502,6 +569,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
502
569
  text,
503
570
  memoryType: params.memoryType,
504
571
  explicitImportance: params.explicitImportance,
572
+ extractedImportanceByKey: params.extractedImportanceByKey,
505
573
  confidenceByKey: params.confidenceByKey,
506
574
  durabilityByKey: params.durabilityByKey,
507
575
  categoryByKey: params.categoryByKey,
@@ -524,6 +592,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
524
592
  text,
525
593
  memoryType: params.memoryType,
526
594
  explicitImportance: params.explicitImportance,
595
+ extractedImportanceByKey: params.extractedImportanceByKey,
527
596
  confidenceByKey: params.confidenceByKey,
528
597
  durabilityByKey: params.durabilityByKey,
529
598
  categoryByKey: params.categoryByKey,
@@ -17,12 +17,23 @@ Classification:
17
17
  - "preference": Subjective preference or style choice.
18
18
  - "decision": Explicit decision, requirement, or policy.
19
19
 
20
+ Storage scoring:
21
+ - importance: 0 to 1 for long-term usefulness.
22
+ - classification: durable | transient | uncertain.
23
+ - rationale: short and concrete.
24
+
20
25
  Confidence scoring:
21
26
  - 1.0: Explicit, confirmed statements ("We use TypeScript", "I prefer dark mode", "We decided to migrate").
22
27
  - 0.8: Strong implications or clearly implied facts.
23
28
  - 0.6-0.7: Reasonable inferences with some ambiguity.
24
29
  - Below 0.6: Do NOT extract. Skip entirely.
25
30
 
31
+ Importance scoring:
32
+ - 0.9-1.0: Foundational business decisions, architecture, durable requirements, critical facts.
33
+ - 0.6-0.8: Stable process or domain knowledge worth reusing later.
34
+ - 0.2-0.5: Low-value or weakly durable memory.
35
+ - Use classification="transient" for low-value facts that should usually be skipped.
36
+
26
37
  Durability classification:
27
38
  - "core": Business decisions, technical architecture choices, confirmed requirements, strategic direction. These are foundational and rarely change. Examples: "We use SurrealDB", "We decided to adopt microservices", "Revenue target is $1M ARR".
28
39
  - "standard": General facts, moderate inferences, process details. These may evolve over time. Examples: "Team has 5 engineers", "Sprint cycle is 2 weeks", "Using Jest for testing".
@@ -33,10 +44,15 @@ Hard rules:
33
44
  - Do NOT extract transient tasks, questions, research requests, or one-off conversation details.
34
45
  - Do NOT turn suggestions, brainstorming, or hypotheticals into facts. If something is not confirmed, skip it.
35
46
  - Do NOT trust new facts introduced by the agent unless the user explicitly confirms them.
47
+ - Use classification="transient" only when the fact is not worth persisting long-term.
36
48
  - Preserve concrete numeric literals and units exactly when present (examples: "6", "50k", "$79", "15%", "Q2 2026").
37
49
  - If a statement explicitly marks an older policy/value as deprecated or obsolete, capture both:
38
50
  1) the current truth
39
51
  2) the deprecation/obsolescence fact
52
+ - For every returned fact, also provide:
53
+ - importance: 0 to 1
54
+ - classification: durable | transient | uncertain
55
+ - rationale: one short sentence
40
56
  - Prefer returning fewer items. If uncertain, return an empty list.
41
57
  - Max ${maxFacts} facts.
42
58
 
@@ -1,4 +1,5 @@
1
1
  import { pluginRuntime } from '../config/agent-defaults'
2
+ import type { LotaRuntimeIndexedRepositoriesContext } from './runtime-extensions'
2
3
  import { getRuntimeAdapters } from './runtime-extensions'
3
4
 
4
5
  export function getPluginService(path: string[]): ((...args: unknown[]) => unknown) | undefined {
@@ -20,8 +21,8 @@ export function getPluginService(path: string[]): ((...args: unknown[]) => unkno
20
21
 
21
22
  export async function buildIndexedRepositoriesContext(
22
23
  organizationId: string,
23
- ): Promise<{ provideRepoTool: boolean; defaultSectionsByAgent: Record<string, unknown>; context: string }> {
24
- const buildContext = getRuntimeAdapters().workstream?.buildIndexedRepositoriesContext
24
+ ): Promise<LotaRuntimeIndexedRepositoriesContext> {
25
+ const buildContext = getRuntimeAdapters().buildIndexedRepositoriesContext
25
26
  if (!buildContext) {
26
27
  return { provideRepoTool: false, defaultSectionsByAgent: {}, context: '' }
27
28
  }
@@ -1,27 +1,10 @@
1
- import type {
2
- CarryForwardPolicy,
3
- CycleSchedule,
4
- PlanDraft,
5
- PlanNodeResult,
6
- PlanNodeSpec,
7
- PlanScheduleSpec,
8
- SignalDeclaration,
9
- } from '@lota-sdk/shared'
1
+ import type { PlanNodeResult, PlanNodeSpec } from '@lota-sdk/shared'
10
2
 
11
3
  import type { RecordIdRef } from '../db/record-id'
12
4
 
13
- export interface PluginContextEnricher {
14
- domain: string
15
- enrich(params: {
16
- objective: string
17
- organizationId: string
18
- }): Promise<{ data: Record<string, unknown>; confidence: number }>
19
- }
20
-
21
5
  export interface LotaPluginContributions {
22
6
  envKeys: readonly string[]
23
7
  schemaFiles: readonly (string | URL)[]
24
- signals?: readonly SignalDeclaration[]
25
8
  }
26
9
 
27
10
  export interface PluginNodeExecutorContext {
@@ -50,33 +33,9 @@ export interface SystemNodeExecutor {
50
33
  executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult>
51
34
  }
52
35
 
53
- export interface PlaybookContribution {
54
- name: string
55
- description: string
56
- tags: string[]
57
- draft: PlanDraft
58
- schedule?: PlanScheduleSpec
59
- cycleSchedule?: CycleSchedule
60
- carryForwardPolicy?: CarryForwardPolicy
61
- }
62
-
63
- export interface PlaybookContributor {
64
- playbooks: readonly PlaybookContribution[]
65
- }
66
-
67
- export interface PluginDomainAgentDefinition {
68
- agentId: string
69
- displayName: string
70
- capabilities: readonly string[]
71
- }
72
-
73
36
  export interface LotaPlugin<TServices = Record<string, unknown>, TTools = Record<string, unknown>> {
74
37
  services: TServices
75
38
  nodeExecutor?: PluginNodeExecutor
76
39
  tools?: TTools
77
40
  contributions: LotaPluginContributions
78
- playbookContributor?: PlaybookContributor
79
- domainAgents?: readonly PluginDomainAgentDefinition[]
80
- contextEnrichers?: readonly PluginContextEnricher[]
81
- onSignal?: (signal: string, payload: unknown, source: string) => Promise<void> | void
82
41
  }
@@ -0,0 +1,212 @@
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
+
3
+ import { agentDisplayNames } from '../config/agent-defaults'
4
+ import type { RecordIdRef } from '../db/record-id'
5
+ import { enqueueMemoryConsolidation } from '../queues/memory-consolidation.queue'
6
+ import { enqueueRegularChatMemoryDigest, enqueueSkillExtraction } from '../queues/organization-learning.queue'
7
+ import { enqueuePostChatMemory } from '../queues/post-chat-memory.queue'
8
+ import { enqueueRecentActivityTitleRefinement } from '../queues/title-generation.queue'
9
+ import {
10
+ shouldEnqueueMemoryConsolidation,
11
+ shouldEnqueueMemoryExtraction,
12
+ shouldEnqueueOnboardingPostChatMemory,
13
+ shouldEnqueueRegularDigestForWorkstream,
14
+ } from '../runtime/memory-digest-policy'
15
+ import { getRuntimeAdapters } from '../runtime/runtime-extensions'
16
+ import { shouldEnqueueSkillExtraction } from '../runtime/skill-extraction-policy'
17
+ import {
18
+ appendPersistedWorkstreamContextToHistoryMessages,
19
+ buildAgentHistoryMessages,
20
+ buildConversationSummary,
21
+ buildReadableUploadMetadataContext,
22
+ extractMessageText,
23
+ readOptionalString,
24
+ toHistoryMessages,
25
+ } from '../runtime/workstream-chat-helpers'
26
+ import { recentActivityService } from '../services/recent-activity.service'
27
+ import { workstreamService } from '../services/workstream.service'
28
+ import type { NormalizedWorkstream, WorkstreamRecord } from '../services/workstream.types'
29
+ import { safeEnqueue } from '../utils/async'
30
+ import { toIsoDateTimeString } from '../utils/date-time'
31
+ import type { WorkstreamState } from './workstream-state'
32
+
33
+ function buildRecentActivityChatDeepLink(params: {
34
+ workstream: NormalizedWorkstream
35
+ workstreamId: string
36
+ visibleAgentId: string
37
+ }): { route: string; search: Record<string, string> } {
38
+ if (params.workstream.mode === 'direct') {
39
+ return { route: 'direct-workstream', search: { workstreamId: params.workstreamId, agentId: params.visibleAgentId } }
40
+ }
41
+
42
+ return { route: 'group-workstream', search: { workstreamId: params.workstreamId } }
43
+ }
44
+
45
+ function buildRecentActivityChatSystemTitle(params: {
46
+ workstream: NormalizedWorkstream
47
+ visibleAgentId: string
48
+ }): string {
49
+ if (params.workstream.mode === 'direct') {
50
+ return `Conversation with ${agentDisplayNames[params.visibleAgentId]}`
51
+ }
52
+
53
+ return params.workstream.title.trim() || 'Workstream update'
54
+ }
55
+
56
+ interface PostTurnSideEffectsParams {
57
+ workstream: NormalizedWorkstream
58
+ workstreamRef: RecordIdRef
59
+ orgRef: RecordIdRef
60
+ userRef: RecordIdRef
61
+ userName?: string | null
62
+ orgIdString: string
63
+ workstreamIdString: string
64
+ onboardingActive: boolean
65
+ workspace: unknown
66
+ allAssistantMessages: ChatMessage[]
67
+ referenceUserMessage: ChatMessage | undefined
68
+ referenceUserMessageId: string
69
+ loadRecentHistory: () => Promise<ChatMessage[]>
70
+ listReadableUploads: () => Array<{
71
+ filename: string
72
+ mediaType: string
73
+ sizeBytes: number | null
74
+ storageKey: string
75
+ }>
76
+ memoryBlock: string
77
+ visibleWorkstreamAgentId: string | null | undefined
78
+ defaultLeadAgentId: string
79
+ latestWorkstreamRecord: WorkstreamRecord
80
+ latestPersistedState: WorkstreamState | null
81
+ isUserTurn: boolean
82
+ }
83
+
84
+ export async function runPostTurnSideEffects(params: PostTurnSideEffectsParams): Promise<void> {
85
+ const recentHistory = await params.loadRecentHistory()
86
+ const turnCount = await workstreamService.incrementTurnCount(params.workstreamRef)
87
+ const agentMessages = buildAgentHistoryMessages(params.allAssistantMessages)
88
+ const historyMessagesForMemory = appendPersistedWorkstreamContextToHistoryMessages(toHistoryMessages(recentHistory), {
89
+ compactionSummary: params.latestWorkstreamRecord.compactionSummary,
90
+ persistedState: params.latestPersistedState,
91
+ })
92
+
93
+ const userMessageText = params.referenceUserMessage ? extractMessageText(params.referenceUserMessage).trim() : ''
94
+ const readableUploads = params.listReadableUploads()
95
+ const attachmentMetadataContext = buildReadableUploadMetadataContext(readableUploads)
96
+ const hasAttachmentContext = Boolean(attachmentMetadataContext)
97
+ const shouldExtractMemory = params.onboardingActive
98
+ ? shouldEnqueueOnboardingPostChatMemory({
99
+ onboardingActive: params.onboardingActive,
100
+ userMessageText,
101
+ hasAttachmentContext,
102
+ agentMessageCount: agentMessages.length,
103
+ })
104
+ : shouldEnqueueMemoryExtraction({ onboardingActive: params.onboardingActive, turnCount }) &&
105
+ userMessageText.length > 0
106
+
107
+ if (shouldExtractMemory) {
108
+ const memoryUserMessage = userMessageText || 'User uploaded attachment(s).'
109
+ await safeEnqueue(
110
+ () =>
111
+ enqueuePostChatMemory({
112
+ orgId: params.orgIdString,
113
+ workstreamId: params.workstreamIdString,
114
+ sourceId: params.referenceUserMessageId,
115
+ onboardStatus: readOptionalString((params.workspace as { onboardStatus?: unknown }).onboardStatus),
116
+ userMessage: memoryUserMessage,
117
+ historyMessages: historyMessagesForMemory,
118
+ agentMessages,
119
+ memoryBlock: params.memoryBlock.trim() ? params.memoryBlock : undefined,
120
+ attachmentContext: attachmentMetadataContext,
121
+ }),
122
+ { operationName: 'post-chat memory extraction enqueue' },
123
+ )
124
+ }
125
+
126
+ if (params.isUserTurn && params.referenceUserMessage) {
127
+ const conversationSummary = buildConversationSummary({
128
+ userMessageText,
129
+ assistantMessages: params.allAssistantMessages,
130
+ })
131
+ if (conversationSummary) {
132
+ const effectiveAgentId = params.visibleWorkstreamAgentId ?? params.defaultLeadAgentId
133
+ const recentActivityResult = await recentActivityService.recordEvent({
134
+ orgId: params.orgRef,
135
+ userId: params.userRef,
136
+ source: 'system',
137
+ event: {
138
+ sourceEventId: `chat-turn:${params.referenceUserMessageId}`,
139
+ kind: 'chat.turn.completed',
140
+ targetKind: 'workstream',
141
+ targetId: params.workstreamIdString,
142
+ mergeKey: `workstream:${params.workstreamIdString}`,
143
+ title: buildRecentActivityChatSystemTitle({
144
+ workstream: params.workstream,
145
+ visibleAgentId: effectiveAgentId,
146
+ }),
147
+ sourceLabel: agentDisplayNames[effectiveAgentId],
148
+ deepLink: buildRecentActivityChatDeepLink({
149
+ workstream: params.workstream,
150
+ workstreamId: params.workstreamIdString,
151
+ visibleAgentId: effectiveAgentId,
152
+ }),
153
+ metadata: {
154
+ agentId: effectiveAgentId,
155
+ agentName: agentDisplayNames[effectiveAgentId],
156
+ workstreamId: params.workstreamIdString,
157
+ workstreamTitle: params.latestWorkstreamRecord.title ?? params.workstream.title,
158
+ workstreamMode: params.workstream.mode,
159
+ ...(params.workstream.coreType ? { coreType: params.workstream.coreType } : {}),
160
+ userMessageText,
161
+ assistantSummary: conversationSummary,
162
+ messageId: params.referenceUserMessageId,
163
+ },
164
+ occurredAt: toIsoDateTimeString(params.referenceUserMessage.metadata?.createdAt ?? Date.now()),
165
+ },
166
+ })
167
+
168
+ await safeEnqueue(
169
+ async () => {
170
+ const enqueuePostChatOrgAction = getRuntimeAdapters().enqueuePostChatOrgAction
171
+ if (!enqueuePostChatOrgAction) {
172
+ return
173
+ }
174
+ const sourceCreatedAt = params.referenceUserMessage?.metadata?.createdAt ?? Date.now()
175
+
176
+ await enqueuePostChatOrgAction({
177
+ orgId: params.orgIdString,
178
+ workstreamId: params.workstreamIdString,
179
+ sourceId: params.referenceUserMessageId,
180
+ sourceCreatedAt,
181
+ conversationSummary,
182
+ })
183
+ },
184
+ { operationName: 'post-chat org action enqueue' },
185
+ )
186
+
187
+ if (recentActivityService.isMeaningfulRefinementCandidate(recentActivityResult.item)) {
188
+ await safeEnqueue(() => enqueueRecentActivityTitleRefinement({ activityId: recentActivityResult.item.id }), {
189
+ operationName: 'recent activity title refinement enqueue',
190
+ })
191
+ }
192
+ }
193
+ }
194
+
195
+ if (shouldEnqueueRegularDigestForWorkstream({ onboardingActive: params.onboardingActive, turnCount })) {
196
+ await safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: params.orgIdString }), {
197
+ operationName: 'regular chat memory digest enqueue',
198
+ })
199
+ }
200
+
201
+ if (shouldEnqueueSkillExtraction({ onboardingActive: params.onboardingActive, turnCount })) {
202
+ await safeEnqueue(() => enqueueSkillExtraction({ orgId: params.orgIdString }), {
203
+ operationName: 'skill extraction enqueue',
204
+ })
205
+ }
206
+
207
+ if (shouldEnqueueMemoryConsolidation({ onboardingActive: params.onboardingActive, turnCount })) {
208
+ await safeEnqueue(() => enqueueMemoryConsolidation({ scopeId: params.orgIdString }), {
209
+ operationName: 'memory consolidation enqueue',
210
+ })
211
+ }
212
+ }
@@ -246,19 +246,6 @@ export const LotaRuntimeConfigSchema = z.object({
246
246
  })
247
247
  .default({ searchK: 6, embeddingCacheTtlSeconds: 7200 }),
248
248
  workstreams: workstreamConfigSchema.default({}),
249
- backgroundProcessing: z
250
- .object({
251
- memoryExtractionFrequency: z.coerce.number().int().positive().default(3),
252
- skillExtractionFrequency: z.coerce.number().int().positive().default(5),
253
- memoryDigestFrequency: z.coerce.number().int().positive().default(1),
254
- memoryConsolidationFrequency: z.coerce.number().int().positive().default(10),
255
- })
256
- .default({
257
- memoryExtractionFrequency: 3,
258
- skillExtractionFrequency: 5,
259
- memoryDigestFrequency: 1,
260
- memoryConsolidationFrequency: 10,
261
- }),
262
249
  agents: agentsConfigSchema,
263
250
  toolProviders: z.custom<Record<string, unknown>>(isToolProviderRecord).optional(),
264
251
  extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
@@ -184,20 +184,14 @@ export interface LotaRuntimeTurnHooks {
184
184
  }
185
185
 
186
186
  export interface LotaRuntimeAdapters {
187
- services?: { workspaceProvider?: LotaRuntimeWorkspaceProvider }
188
- events?: { planEventAdapter?: LotaRuntimePlanEventAdapter }
189
- workstream?: {
190
- buildIndexedRepositoriesContext?: (workspaceId: string) => Promise<LotaRuntimeIndexedRepositoriesContext>
191
- buildTeamThinkAgentTools?: (params: LotaRuntimeTeamThinkToolsParams) => Promise<{ tools: ToolSet }>
192
- }
193
- queues?: {
194
- enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void>
195
- enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
196
- }
197
- workers?: {
198
- connectPluginDatabases?: () => Promise<void>
199
- withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
200
- }
187
+ workspaceProvider?: LotaRuntimeWorkspaceProvider
188
+ planEventAdapter?: LotaRuntimePlanEventAdapter
189
+ buildIndexedRepositoriesContext?: (workspaceId: string) => Promise<LotaRuntimeIndexedRepositoriesContext>
190
+ buildTeamThinkAgentTools?: (params: LotaRuntimeTeamThinkToolsParams) => Promise<{ tools: ToolSet }>
191
+ enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void>
192
+ enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
193
+ connectPluginDatabases?: () => Promise<void>
194
+ withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
201
195
  }
202
196
 
203
197
  interface RuntimeExtensionsState {
@@ -243,11 +237,11 @@ export function getToolProviders(): ToolSet {
243
237
  }
244
238
 
245
239
  export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) | undefined {
246
- return runtimeExtensionsState.adapters.workers?.connectPluginDatabases
240
+ return runtimeExtensionsState.adapters.connectPluginDatabases
247
241
  }
248
242
 
249
243
  export async function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
250
- const adapter = runtimeExtensionsState.adapters.workers?.withWorkspaceMemoryLock
244
+ const adapter = runtimeExtensionsState.adapters.withWorkspaceMemoryLock
251
245
  if (!adapter) {
252
246
  return fn()
253
247
  }
@@ -2,31 +2,26 @@ import { startAutonomousJobWorker } from '../queues/autonomous-job.queue'
2
2
  import { startContextCompactionWorker } from '../queues/context-compaction.queue'
3
3
  import { startDelayedNodePromotionWorker } from '../queues/delayed-node-promotion.queue'
4
4
  import { scheduleRecurringConsolidation, startMemoryConsolidationWorker } from '../queues/memory-consolidation.queue'
5
- import { schedulePlanAgentHeartbeatSweep, startPlanAgentHeartbeatWorker } from '../queues/plan-agent-heartbeat.queue'
5
+ import { startOrganizationLearningWorker } from '../queues/organization-learning.queue'
6
+ import { startPlanAgentHeartbeatWorker } from '../queues/plan-agent-heartbeat.queue'
6
7
  import { startPlanSchedulerWorker } from '../queues/plan-scheduler.queue'
7
8
  import { startPostChatMemoryWorker } from '../queues/post-chat-memory.queue'
8
- import { startRecentActivityTitleRefinementWorker } from '../queues/recent-activity-title-refinement.queue'
9
- import { startRegularChatMemoryDigestWorker } from '../queues/regular-chat-memory-digest.queue'
10
- import { startSkillExtractionWorker } from '../queues/skill-extraction.queue'
11
- import { startWorkstreamTitleGenerationWorker } from '../queues/workstream-title-generation.queue'
9
+ import { startTitleGenerationWorker } from '../queues/title-generation.queue'
12
10
 
13
11
  export interface LotaRuntimeWorkerStartRegistry {
14
12
  autonomousJob: typeof startAutonomousJobWorker
15
13
  contextCompaction: typeof startContextCompactionWorker
16
14
  delayedNodePromotion: typeof startDelayedNodePromotionWorker
17
15
  memoryConsolidation: typeof startMemoryConsolidationWorker
16
+ organizationLearning: typeof startOrganizationLearningWorker
18
17
  planAgentHeartbeat: typeof startPlanAgentHeartbeatWorker
19
18
  planScheduler: typeof startPlanSchedulerWorker
20
19
  postChatMemory: typeof startPostChatMemoryWorker
21
- regularChatMemoryDigest: typeof startRegularChatMemoryDigestWorker
22
- skillExtraction: typeof startSkillExtractionWorker
23
- workstreamTitleGeneration: typeof startWorkstreamTitleGenerationWorker
24
- recentActivityTitleRefinement: typeof startRecentActivityTitleRefinementWorker
20
+ titleGeneration: typeof startTitleGenerationWorker
25
21
  }
26
22
 
27
23
  export interface LotaRuntimeWorkerScheduleRegistry {
28
24
  recurringConsolidation: typeof scheduleRecurringConsolidation
29
- planAgentHeartbeatSweep: typeof schedulePlanAgentHeartbeatSweep
30
25
  }
31
26
 
32
27
  export interface LotaRuntimeWorkers {
@@ -46,19 +41,13 @@ export function buildRuntimeWorkerRegistry(extraWorkers?: LotaRuntimeWorkerExten
46
41
  contextCompaction: startContextCompactionWorker,
47
42
  delayedNodePromotion: startDelayedNodePromotionWorker,
48
43
  memoryConsolidation: startMemoryConsolidationWorker,
44
+ organizationLearning: startOrganizationLearningWorker,
49
45
  planAgentHeartbeat: startPlanAgentHeartbeatWorker,
50
46
  planScheduler: startPlanSchedulerWorker,
51
47
  postChatMemory: startPostChatMemoryWorker,
52
- regularChatMemoryDigest: startRegularChatMemoryDigestWorker,
53
- skillExtraction: startSkillExtractionWorker,
54
- workstreamTitleGeneration: startWorkstreamTitleGenerationWorker,
55
- recentActivityTitleRefinement: startRecentActivityTitleRefinementWorker,
48
+ titleGeneration: startTitleGenerationWorker,
56
49
  ...extraWorkers?.start,
57
50
  },
58
- schedule: {
59
- recurringConsolidation: scheduleRecurringConsolidation,
60
- planAgentHeartbeatSweep: schedulePlanAgentHeartbeatSweep,
61
- ...extraWorkers?.schedule,
62
- },
51
+ schedule: { recurringConsolidation: scheduleRecurringConsolidation, ...extraWorkers?.schedule },
63
52
  }
64
53
  }