@lota-sdk/core 0.4.9 → 0.4.10

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 (158) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +38 -10
  4. package/src/config/agent-defaults.ts +22 -9
  5. package/src/config/agent-types.ts +1 -1
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/index.ts +0 -1
  8. package/src/config/logger.ts +20 -7
  9. package/src/config/thread-defaults.ts +12 -4
  10. package/src/create-runtime.ts +69 -656
  11. package/src/db/memory-query-builder.ts +2 -1
  12. package/src/db/memory-store.ts +29 -20
  13. package/src/db/memory.ts +188 -195
  14. package/src/db/service-normalization.ts +97 -64
  15. package/src/db/service.ts +706 -538
  16. package/src/db/startup.ts +30 -19
  17. package/src/effect/awaitable-effect.ts +46 -37
  18. package/src/effect/helpers.ts +30 -5
  19. package/src/effect/index.ts +7 -5
  20. package/src/effect/layers.ts +82 -72
  21. package/src/effect/runtime.ts +18 -3
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -66
  24. package/src/index.ts +13 -11
  25. package/src/queues/autonomous-job.queue.ts +59 -71
  26. package/src/queues/context-compaction.queue.ts +6 -18
  27. package/src/queues/delayed-node-promotion.queue.ts +9 -17
  28. package/src/queues/organization-learning.queue.ts +17 -4
  29. package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
  30. package/src/queues/plan-scheduler.queue.ts +6 -18
  31. package/src/queues/post-chat-memory.queue.ts +6 -18
  32. package/src/queues/queue-factory.ts +128 -50
  33. package/src/queues/title-generation.queue.ts +6 -17
  34. package/src/redis/connection.ts +181 -164
  35. package/src/redis/runtime-connection.ts +13 -3
  36. package/src/redis/stream-context.ts +17 -9
  37. package/src/runtime/agent-runtime-policy.ts +1 -1
  38. package/src/runtime/agent-stream-helpers.ts +15 -11
  39. package/src/runtime/chat-run-orchestration.ts +1 -1
  40. package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
  41. package/src/runtime/context-compaction/context-compaction.ts +126 -82
  42. package/src/runtime/domain-layer.ts +192 -0
  43. package/src/runtime/graph-designer.ts +15 -7
  44. package/src/runtime/helper-model.ts +8 -4
  45. package/src/runtime/index.ts +0 -1
  46. package/src/runtime/memory/memory-block.ts +19 -9
  47. package/src/runtime/memory/memory-pipeline.ts +53 -66
  48. package/src/runtime/memory/memory-scope.ts +33 -29
  49. package/src/runtime/plugin-resolution.ts +33 -54
  50. package/src/runtime/post-turn-side-effects.ts +6 -26
  51. package/src/runtime/retrieval-adapters.ts +4 -4
  52. package/src/runtime/runtime-accessors.ts +92 -0
  53. package/src/runtime/runtime-config.ts +3 -3
  54. package/src/runtime/runtime-extensions.ts +20 -9
  55. package/src/runtime/runtime-lifecycle.ts +124 -0
  56. package/src/runtime/runtime-services.ts +386 -0
  57. package/src/runtime/runtime-token.ts +47 -0
  58. package/src/runtime/social-chat/social-chat-agent-runner.ts +7 -5
  59. package/src/runtime/social-chat/social-chat-history.ts +21 -12
  60. package/src/runtime/social-chat/social-chat.ts +401 -365
  61. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
  62. package/src/runtime/thread-turn-context.ts +21 -27
  63. package/src/services/agent-activity.service.ts +1 -1
  64. package/src/services/agent-executor.service.ts +179 -187
  65. package/src/services/artifact.service.ts +10 -5
  66. package/src/services/attachment.service.ts +35 -1
  67. package/src/services/autonomous-job.service.ts +58 -56
  68. package/src/services/background-work.service.ts +54 -0
  69. package/src/services/chat-run-registry.service.ts +3 -1
  70. package/src/services/context-compaction.service.ts +1 -1
  71. package/src/services/document-chunk.service.ts +8 -17
  72. package/src/services/execution-plan/execution-plan-graph.ts +74 -52
  73. package/src/services/execution-plan/execution-plan.service.ts +1 -1
  74. package/src/services/feedback-loop.service.ts +1 -1
  75. package/src/services/global-orchestrator.service.ts +33 -10
  76. package/src/services/graph-full-routing.ts +44 -33
  77. package/src/services/index.ts +1 -0
  78. package/src/services/institutional-memory.service.ts +8 -17
  79. package/src/services/learned-skill.service.ts +38 -35
  80. package/src/services/memory/memory-errors.ts +27 -0
  81. package/src/services/memory/memory-org-memory.ts +14 -3
  82. package/src/services/memory/memory-preseeded.ts +10 -4
  83. package/src/services/memory/memory-utils.ts +2 -1
  84. package/src/services/memory/memory.service.ts +26 -44
  85. package/src/services/memory/rerank.service.ts +3 -11
  86. package/src/services/monitoring-window.service.ts +1 -1
  87. package/src/services/mutating-approval.service.ts +1 -1
  88. package/src/services/node-workspace.service.ts +2 -2
  89. package/src/services/notification.service.ts +16 -4
  90. package/src/services/organization-member.service.ts +1 -1
  91. package/src/services/organization.service.ts +34 -51
  92. package/src/services/ownership-dispatcher.service.ts +132 -90
  93. package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
  94. package/src/services/plan/plan-agent-query.service.ts +1 -1
  95. package/src/services/plan/plan-approval.service.ts +52 -48
  96. package/src/services/plan/plan-artifact.service.ts +2 -2
  97. package/src/services/plan/plan-builder.service.ts +2 -2
  98. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  99. package/src/services/plan/plan-compiler.service.ts +1 -1
  100. package/src/services/plan/plan-completion-side-effects.ts +18 -24
  101. package/src/services/plan/plan-coordination.service.ts +1 -1
  102. package/src/services/plan/plan-cycle.service.ts +171 -164
  103. package/src/services/plan/plan-deadline.service.ts +290 -304
  104. package/src/services/plan/plan-event-delivery.service.ts +44 -39
  105. package/src/services/plan/plan-executor-graph.ts +114 -67
  106. package/src/services/plan/plan-executor-helpers.ts +60 -75
  107. package/src/services/plan/plan-executor.service.ts +550 -467
  108. package/src/services/plan/plan-run.service.ts +12 -19
  109. package/src/services/plan/plan-scheduler.service.ts +27 -33
  110. package/src/services/plan/plan-template.service.ts +1 -1
  111. package/src/services/plan/plan-transaction-events.ts +8 -5
  112. package/src/services/plan/plan-validator.service.ts +1 -1
  113. package/src/services/plan/plan-workspace.service.ts +17 -11
  114. package/src/services/plugin-executor.service.ts +26 -21
  115. package/src/services/quality-metrics.service.ts +1 -1
  116. package/src/services/queue-job.service.ts +8 -17
  117. package/src/services/recent-activity-title.service.ts +17 -9
  118. package/src/services/recent-activity.service.ts +1 -1
  119. package/src/services/skill-resolver.service.ts +1 -1
  120. package/src/services/social-chat-history.service.ts +37 -20
  121. package/src/services/system-executor.service.ts +25 -20
  122. package/src/services/thread/thread-bootstrap.ts +26 -10
  123. package/src/services/thread/thread-listing.ts +2 -1
  124. package/src/services/thread/thread-memory-block.ts +18 -5
  125. package/src/services/thread/thread-message.service.ts +24 -8
  126. package/src/services/thread/thread-title.service.ts +1 -1
  127. package/src/services/thread/thread-turn-execution.ts +1 -1
  128. package/src/services/thread/thread-turn-preparation.service.ts +18 -16
  129. package/src/services/thread/thread-turn-streaming.ts +12 -11
  130. package/src/services/thread/thread-turn.ts +43 -10
  131. package/src/services/thread/thread.service.ts +11 -2
  132. package/src/services/user.service.ts +1 -1
  133. package/src/services/write-intent-validator.service.ts +1 -1
  134. package/src/storage/attachment-storage.service.ts +7 -4
  135. package/src/storage/generated-document-storage.service.ts +1 -1
  136. package/src/system-agents/context-compaction.agent.ts +1 -1
  137. package/src/system-agents/helper-agent-options.ts +1 -1
  138. package/src/system-agents/memory-reranker.agent.ts +1 -1
  139. package/src/system-agents/memory.agent.ts +1 -1
  140. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  141. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  142. package/src/system-agents/skill-extractor.agent.ts +1 -1
  143. package/src/system-agents/skill-manager.agent.ts +1 -1
  144. package/src/system-agents/title-generator.agent.ts +1 -1
  145. package/src/tools/execution-plan.tool.ts +28 -17
  146. package/src/tools/fetch-webpage.tool.ts +20 -13
  147. package/src/tools/firecrawl-client.ts +13 -3
  148. package/src/tools/plan-approval.tool.ts +9 -1
  149. package/src/tools/search-web.tool.ts +16 -9
  150. package/src/tools/team-think.tool.ts +2 -2
  151. package/src/utils/async.ts +15 -6
  152. package/src/utils/errors.ts +27 -15
  153. package/src/workers/bootstrap.ts +25 -48
  154. package/src/workers/organization-learning.worker.ts +1 -1
  155. package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
  156. package/src/workers/worker-utils.ts +20 -2
  157. package/src/config/search.ts +0 -3
  158. package/src/runtime/agent-types.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -31,7 +31,7 @@
31
31
  "@ai-sdk/openai": "^3.0.53",
32
32
  "@chat-adapter/slack": "^4.26.0",
33
33
  "@chat-adapter/state-ioredis": "^4.26.0",
34
- "@lota-sdk/shared": "0.4.9",
34
+ "@lota-sdk/shared": "0.4.10",
35
35
  "@mendable/firecrawl-js": "^4.18.3",
36
36
  "@surrealdb/node": "^3.0.3",
37
37
  "ai": "^6.0.167",
@@ -107,7 +107,9 @@ export class EmbeddingCache {
107
107
  }
108
108
  }
109
109
 
110
- export class EmbeddingCacheTag extends Context.Service<EmbeddingCacheTag, EmbeddingCache>()('EmbeddingCache') {}
110
+ export class EmbeddingCacheTag extends Context.Service<EmbeddingCacheTag, EmbeddingCache>()(
111
+ '@lota-sdk/core/EmbeddingCache',
112
+ ) {}
111
113
 
112
114
  export const EmbeddingCacheLive = Layer.effect(
113
115
  EmbeddingCacheTag,
@@ -6,7 +6,7 @@ import { Cause, Clock, Context, Duration, Effect, ExecutionPlan, Fiber, Layer, S
6
6
 
7
7
  import { DEFAULT_AI_GATEWAY_URL } from '../config/constants'
8
8
  import { AiGenerationError, ConfigurationError } from '../effect/errors'
9
- import { getLotaSdkRuntime } from '../effect/runtime'
9
+ import { resolveLotaService } from '../effect/runtime'
10
10
  import { RuntimeConfigServiceTag } from '../effect/services'
11
11
  import { getDirectOpenRouterProvider, normalizeDirectOpenRouterModelId } from '../openrouter/direct-provider'
12
12
  import { isRecord, readString } from '../utils/string'
@@ -28,12 +28,12 @@ type AiGatewayAttemptResult<A> = { source: string; result: A }
28
28
  class AiGatewayGenerateAttempt extends Context.Service<
29
29
  AiGatewayGenerateAttempt,
30
30
  { readonly execute: Effect.Effect<AiGatewayAttemptResult<AiGatewayGenerateResult>, AiGenerationError> }
31
- >()('AiGatewayGenerateAttempt') {}
31
+ >()('@lota-sdk/core/internal/AiGatewayGenerateAttempt') {}
32
32
 
33
33
  class AiGatewayStreamAttempt extends Context.Service<
34
34
  AiGatewayStreamAttempt,
35
35
  { readonly execute: Effect.Effect<AiGatewayAttemptResult<AiGatewayStreamResult>, AiGenerationError> }
36
- >()('AiGatewayStreamAttempt') {}
36
+ >()('@lota-sdk/core/internal/AiGatewayStreamAttempt') {}
37
37
 
38
38
  const EXPECTED_GATEWAY_KEY_PREFIX = 'sk-bf-'
39
39
  const AI_GATEWAY_VIRTUAL_KEY_HEADER = 'x-bf-vk'
@@ -69,6 +69,10 @@ const RETRYABLE_NETWORK_ERROR_PATTERNS = [
69
69
  /timed out/i,
70
70
  ]
71
71
 
72
+ function isAiGenerationError(error: unknown): error is AiGenerationError {
73
+ return isRecord(error) && error._tag === 'AiGenerationError'
74
+ }
75
+
72
76
  function getNumericField(value: Record<string, unknown>, key: string): number | null {
73
77
  const field = value[key]
74
78
  if (typeof field === 'number' && Number.isFinite(field)) return field
@@ -156,7 +160,7 @@ function stringifyProviderField(value: unknown, maxLength: number): string | und
156
160
  }
157
161
 
158
162
  function classifyAiGatewayError(source: string, error: unknown): AiGenerationError {
159
- if (error instanceof AiGenerationError) {
163
+ if (isAiGenerationError(error)) {
160
164
  return error
161
165
  }
162
166
 
@@ -465,7 +469,7 @@ function normalizeAiGatewayUrl(value: string): string {
465
469
  export class AiGatewayTag extends Context.Service<
466
470
  AiGatewayTag,
467
471
  { readonly semaphore: Semaphore.Semaphore; readonly provider: ReturnType<typeof createOpenAI> }
468
- >()('AiGateway') {}
472
+ >()('@lota-sdk/core/AiGateway') {}
469
473
 
470
474
  export const AiGatewayLive = Layer.effect(
471
475
  AiGatewayTag,
@@ -487,12 +491,30 @@ export const AiGatewayLive = Layer.effect(
487
491
  }),
488
492
  )
489
493
 
490
- function resolveFromRuntime<I, T>(tag: Context.Key<I, T>): T {
491
- return getLotaSdkRuntime().runSync(Effect.service(tag))
494
+ type AiGatewayRuntimeConfig = Context.Service.Shape<typeof RuntimeConfigServiceTag>
495
+
496
+ let currentAiGateway: AiGatewayTag['Service'] | null = null
497
+ let currentAiGatewayRuntimeConfig: AiGatewayRuntimeConfig | null = null
498
+
499
+ export function configureAiGatewayRuntimeAccessors(params: {
500
+ aiGateway: AiGatewayTag['Service']
501
+ runtimeConfig: AiGatewayRuntimeConfig
502
+ }): void {
503
+ currentAiGateway = params.aiGateway
504
+ currentAiGatewayRuntimeConfig = params.runtimeConfig
505
+ }
506
+
507
+ export function clearAiGatewayRuntimeAccessors(): void {
508
+ currentAiGateway = null
509
+ currentAiGatewayRuntimeConfig = null
492
510
  }
493
511
 
494
512
  function getAiGateway(): AiGatewayTag['Service'] {
495
- return resolveFromRuntime(AiGatewayTag)
513
+ return currentAiGateway ?? resolveLotaService(AiGatewayTag)
514
+ }
515
+
516
+ function getAiGatewayRuntimeConfig(): AiGatewayRuntimeConfig {
517
+ return currentAiGatewayRuntimeConfig ?? resolveLotaService(RuntimeConfigServiceTag)
496
518
  }
497
519
 
498
520
  function withAiGatewayConcurrency<A>(effect: Effect.Effect<A, AiGenerationError>): Effect.Effect<A, AiGenerationError> {
@@ -508,6 +530,11 @@ function withAiGatewayStreamConcurrency(
508
530
  const currentContext = yield* Effect.context<never>()
509
531
  yield* semaphore.take(1)
510
532
 
533
+ // NOTE: manual release intentional — permit outlives Effect scope for the
534
+ // stream lifetime. The stream consumer drains asynchronously after this
535
+ // Effect resolves; the permit is released by either the idle-timeout
536
+ // finalize callback or the error path below. The `released` guard makes
537
+ // the release idempotent across those paths.
511
538
  let released = false
512
539
  const release = () => {
513
540
  if (released) return
@@ -517,6 +544,7 @@ function withAiGatewayStreamConcurrency(
517
544
 
518
545
  const attempt = yield* restore(effect).pipe(
519
546
  Effect.catchTag('AiGenerationError', (error) => Effect.sync(release).pipe(Effect.andThen(Effect.fail(error)))),
547
+ Effect.onInterrupt(() => Effect.sync(release)),
520
548
  )
521
549
 
522
550
  return {
@@ -611,12 +639,12 @@ function isOpenRouterModel(modelId: string): boolean {
611
639
  }
612
640
 
613
641
  function hasDirectOpenRouterFallback(modelId: string): boolean {
614
- const config = resolveFromRuntime(RuntimeConfigServiceTag)
642
+ const config = getAiGatewayRuntimeConfig()
615
643
  return isOpenRouterModel(modelId) && Boolean(config.aiGateway.openRouterApiKey?.trim())
616
644
  }
617
645
 
618
646
  function getDirectOpenRouterChatModel(modelId: string): AiGatewayLanguageModel {
619
- const config = resolveFromRuntime(RuntimeConfigServiceTag)
647
+ const config = getAiGatewayRuntimeConfig()
620
648
  return getDirectOpenRouterProvider(config.aiGateway.openRouterApiKey).chat(normalizeDirectOpenRouterModelId(modelId))
621
649
  }
622
650
 
@@ -1,8 +1,7 @@
1
1
  import type { ToolSet } from 'ai'
2
- import { Effect } from 'effect'
3
2
 
4
3
  import { ConfigurationError } from '../effect/errors'
5
- import { getCurrentRuntime } from '../effect/runtime-ref'
4
+ import { resolveLotaService } from '../effect/runtime'
6
5
  import { AgentConfigServiceTag, AgentFactoryServiceTag } from '../effect/services'
7
6
  import type {
8
7
  AgentFactory,
@@ -131,12 +130,28 @@ export interface CoreThreadProfile {
131
130
  instructions: string
132
131
  }
133
132
 
133
+ let currentResolvedAgentConfig: ResolvedAgentConfig | null = null
134
+ let currentResolvedAgentFactoryConfig: ResolvedAgentFactoryConfig | null = null
135
+
136
+ export function configureAgentRuntimeDefaults(params: {
137
+ agentConfig: ResolvedAgentConfig
138
+ agentFactoryConfig: ResolvedAgentFactoryConfig
139
+ }): void {
140
+ currentResolvedAgentConfig = params.agentConfig
141
+ currentResolvedAgentFactoryConfig = params.agentFactoryConfig
142
+ }
143
+
144
+ export function clearAgentRuntimeDefaults(): void {
145
+ currentResolvedAgentConfig = null
146
+ currentResolvedAgentFactoryConfig = null
147
+ }
148
+
134
149
  function resolveAgentConfigFromRuntime(): ResolvedAgentConfig {
135
- return getCurrentRuntime().runSync(Effect.service(AgentConfigServiceTag))
150
+ return currentResolvedAgentConfig ?? resolveLotaService(AgentConfigServiceTag)
136
151
  }
137
152
 
138
153
  function resolveAgentFactoryConfigFromRuntime(): ResolvedAgentFactoryConfig {
139
- return getCurrentRuntime().runSync(Effect.service(AgentFactoryServiceTag))
154
+ return currentResolvedAgentFactoryConfig ?? resolveLotaService(AgentFactoryServiceTag)
140
155
  }
141
156
 
142
157
  export function getResolvedAgentConfig(): ResolvedAgentConfig {
@@ -220,18 +235,16 @@ export interface AgentMentionMatch {
220
235
  length: number
221
236
  }
222
237
 
223
- export function extractAgentMentions(
224
- message: string,
225
- agentConfig: ResolvedAgentConfig = resolveAgentConfigFromRuntime(),
226
- ): AgentMentionMatch[] {
238
+ export function extractAgentMentions(message: string, agentConfig?: ResolvedAgentConfig): AgentMentionMatch[] {
227
239
  const matches: AgentMentionMatch[] = []
228
240
  if (!message.trim()) return matches
229
241
 
242
+ const resolvedConfig = agentConfig ?? resolveAgentConfigFromRuntime()
230
243
  const regex = new RegExp(AGENT_MENTION_REGEX)
231
244
  for (const rawMatch of message.matchAll(regex)) {
232
245
  const prefix = rawMatch[1]
233
246
  const rawAgent = rawMatch[2].toLowerCase()
234
- if (!agentConfig.rosterSet.has(rawAgent)) continue
247
+ if (!resolvedConfig.rosterSet.has(rawAgent)) continue
235
248
 
236
249
  const index = rawMatch.index + prefix.length
237
250
  matches.push({ agent: rawAgent, mention: `@${rawAgent}`, index, length: rawAgent.length + 1 })
@@ -1,8 +1,8 @@
1
+ import type { ChatMode, CreateRoutedAgentOptions } from '@lota-sdk/shared'
1
2
  import type { ToolLoopAgent, ToolSet } from 'ai'
2
3
 
3
4
  import type { RecordIdRef } from '../db/record-id'
4
5
  import type { AgentRuntimeConfig, AgentRuntimeRuleOptions } from '../runtime/agent-runtime-policy'
5
- import type { ChatMode, CreateRoutedAgentOptions } from '../runtime/agent-types'
6
6
 
7
7
  export interface AgentToolBuilderParams {
8
8
  agentId: string
@@ -12,7 +12,7 @@ const DEFAULT_CONFIG: BackgroundProcessingConfig = {
12
12
  memoryConsolidationFrequency: 10,
13
13
  }
14
14
 
15
- let resolvedConfig: BackgroundProcessingConfig = { ...DEFAULT_CONFIG }
15
+ const resolvedConfig: BackgroundProcessingConfig = { ...DEFAULT_CONFIG }
16
16
 
17
17
  export function getBackgroundProcessingConfig(): BackgroundProcessingConfig {
18
18
  return resolvedConfig
@@ -4,5 +4,4 @@ export * from './background-processing'
4
4
  export * from './constants'
5
5
  export * from './logger'
6
6
  export * from './model-constants'
7
- export * from './search'
8
7
  export * from './thread-defaults'
@@ -1,5 +1,7 @@
1
1
  import { Effect, Logger, References } from 'effect'
2
2
 
3
+ import { getOptionalCurrentRuntime } from '../effect/runtime-ref'
4
+
3
5
  const LOG_CATEGORY = 'lota-sdk'
4
6
 
5
7
  export type LotaLogLevel = 'trace' | 'debug' | 'info' | 'warning' | 'error' | 'fatal'
@@ -44,6 +46,10 @@ function readEnvLogLevel(): LotaLogLevel | null {
44
46
  return resolveLotaLogLevel(value)
45
47
  }
46
48
 
49
+ // Module-level cache: loggers expose a sync `.info(...)` API that must work
50
+ // from both Effect and non-Effect call sites, so the level cannot live in
51
+ // Effect.Config (which requires Effect context to read). `configureLotaLogger`
52
+ // is called once from `buildInfrastructureLayer` with the resolved value.
47
53
  let configuredLogLevel: LotaLogLevel = readEnvLogLevel() ?? 'info'
48
54
 
49
55
  export function configureLotaLogger(logLevel: LotaLogLevel = 'info'): void {
@@ -222,19 +228,17 @@ function emitConsoleFallback(
222
228
  level: LotaLogLevel,
223
229
  category: readonly string[],
224
230
  message: string,
225
- fields?: LogFields,
231
+ annotations: Record<string, unknown>,
226
232
  ): void {
227
- if (!shouldLog(level)) return
228
-
229
233
  const formattedMessage = `[${category.join(':')}] ${message}`
230
- const context = buildLogContext(fields)
231
234
  const sink = getConsoleMethod(level)
232
- if (Object.keys(context).length === 0) {
235
+ const { lotaLogger: _lotaLogger, ...rest } = annotations
236
+ if (Object.keys(rest).length === 0) {
233
237
  sink(formattedMessage)
234
238
  return
235
239
  }
236
240
 
237
- sink(formattedMessage, context)
241
+ sink(formattedMessage, rest)
238
242
  }
239
243
 
240
244
  function emit(level: LotaLogLevel, category: readonly string[], message: string, fields?: LogFields): void {
@@ -244,8 +248,17 @@ function emit(level: LotaLogLevel, category: readonly string[], message: string,
244
248
  const annotations = { lotaLogger: category.join(':'), ...buildLogContext(fields) }
245
249
  const effect = getLogEffect(level, formattedMessage).pipe(Effect.annotateLogs(annotations))
246
250
 
251
+ // Route through the seated managed runtime so log timestamps stay on the
252
+ // caller's fiber and inherit its span/log annotations. Pre-bootstrap (no
253
+ // runtime seated) uses the standalone Effect logger so output stays
254
+ // consistent with post-bootstrap formatting.
255
+ const runtime = getOptionalCurrentRuntime()
247
256
  try {
248
- runStandaloneLogEffect(effect)
257
+ if (runtime) {
258
+ runtime.runFork(effect)
259
+ } else {
260
+ runStandaloneLogEffect(effect)
261
+ }
249
262
  } catch {
250
263
  emitConsoleFallback(level, category, formattedMessage, annotations)
251
264
  }
@@ -1,6 +1,4 @@
1
- import { Effect } from 'effect'
2
-
3
- import { getCurrentRuntime } from '../effect/runtime-ref'
1
+ import { resolveLotaService } from '../effect/runtime'
4
2
  import { ThreadConfigServiceTag } from '../effect/services'
5
3
 
6
4
  export interface ThreadBootstrapWelcomeConfig {
@@ -30,6 +28,16 @@ interface ResolvedThreadBootstrapConfig {
30
28
 
31
29
  export type { ResolvedThreadBootstrapConfig }
32
30
 
31
+ let currentThreadBootstrapConfig: ResolvedThreadBootstrapConfig | null = null
32
+
33
+ export function configureThreadRuntimeDefaults(config: ResolvedThreadBootstrapConfig): void {
34
+ currentThreadBootstrapConfig = config
35
+ }
36
+
37
+ export function clearThreadRuntimeDefaults(): void {
38
+ currentThreadBootstrapConfig = null
39
+ }
40
+
33
41
  function normalizeThreadAgentIds(values: readonly string[]): readonly string[] {
34
42
  const seen = new Set<string>()
35
43
  const deduped: string[] = []
@@ -73,7 +81,7 @@ export function resolveThreadConfig(params: {
73
81
  }
74
82
 
75
83
  export function getThreadBootstrapConfig(): ResolvedThreadBootstrapConfig {
76
- return getCurrentRuntime().runSync(Effect.service(ThreadConfigServiceTag))
84
+ return currentThreadBootstrapConfig ?? resolveLotaService(ThreadConfigServiceTag)
77
85
  }
78
86
 
79
87
  export function resolveOnboardingOwnerAgentId(