@lota-sdk/core 0.4.10 → 0.4.12

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 (110) hide show
  1. package/package.json +3 -3
  2. package/src/ai-gateway/ai-gateway.ts +214 -98
  3. package/src/ai-gateway/index.ts +16 -1
  4. package/src/config/agent-defaults.ts +4 -120
  5. package/src/config/logger.ts +18 -34
  6. package/src/config/model-constants.ts +1 -0
  7. package/src/config/thread-defaults.ts +1 -18
  8. package/src/create-runtime.ts +90 -28
  9. package/src/db/base.service.ts +30 -38
  10. package/src/db/service.ts +489 -545
  11. package/src/effect/index.ts +0 -2
  12. package/src/effect/layers.ts +6 -13
  13. package/src/embeddings/provider.ts +2 -7
  14. package/src/index.ts +4 -5
  15. package/src/queues/autonomous-job.queue.ts +159 -113
  16. package/src/queues/context-compaction.queue.ts +39 -25
  17. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  18. package/src/queues/document-processor.queue.ts +5 -3
  19. package/src/queues/index.ts +1 -0
  20. package/src/queues/memory-consolidation.queue.ts +79 -53
  21. package/src/queues/organization-learning.queue.ts +63 -39
  22. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  23. package/src/queues/plan-scheduler.queue.ts +100 -84
  24. package/src/queues/post-chat-memory.queue.ts +55 -33
  25. package/src/queues/queue-factory.ts +40 -41
  26. package/src/queues/queues.service.ts +61 -0
  27. package/src/queues/title-generation.queue.ts +42 -31
  28. package/src/redis/org-memory-lock.ts +24 -9
  29. package/src/redis/redis-lease-lock.ts +8 -1
  30. package/src/runtime/agent-identity-overrides.ts +7 -3
  31. package/src/runtime/agent-runtime-policy.ts +9 -4
  32. package/src/runtime/agent-stream-helpers.ts +9 -4
  33. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  34. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  35. package/src/runtime/domain-layer.ts +15 -4
  36. package/src/runtime/execution-plan-visibility.ts +5 -2
  37. package/src/runtime/graph-designer.ts +0 -22
  38. package/src/runtime/index.ts +2 -0
  39. package/src/runtime/indexed-repositories-policy.ts +2 -6
  40. package/src/runtime/live-turn-trace.ts +344 -0
  41. package/src/runtime/plugin-resolution.ts +29 -12
  42. package/src/runtime/post-turn-side-effects.ts +139 -141
  43. package/src/runtime/runtime-config.ts +0 -6
  44. package/src/runtime/runtime-extensions.ts +0 -54
  45. package/src/runtime/runtime-lifecycle.ts +4 -4
  46. package/src/runtime/runtime-services.ts +125 -53
  47. package/src/runtime/runtime-worker-registry.ts +113 -30
  48. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  49. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  50. package/src/runtime/social-chat/social-chat.ts +35 -20
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  52. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  53. package/src/runtime/thread-chat-helpers.ts +18 -9
  54. package/src/runtime/thread-turn-context.ts +7 -47
  55. package/src/runtime/turn-lifecycle.ts +6 -14
  56. package/src/services/agent-activity.service.ts +168 -175
  57. package/src/services/agent-executor.service.ts +35 -16
  58. package/src/services/attachment.service.ts +4 -70
  59. package/src/services/autonomous-job.service.ts +53 -61
  60. package/src/services/context-compaction.service.ts +7 -9
  61. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  62. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  63. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  64. package/src/services/global-orchestrator.service.ts +18 -7
  65. package/src/services/graph-full-routing.ts +7 -6
  66. package/src/services/memory/memory-conversation.ts +10 -5
  67. package/src/services/memory/memory.service.ts +11 -8
  68. package/src/services/ownership-dispatcher.service.ts +16 -5
  69. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  70. package/src/services/plan/plan-agent-query.service.ts +12 -8
  71. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  72. package/src/services/plan/plan-cycle.service.ts +7 -45
  73. package/src/services/plan/plan-deadline.service.ts +28 -17
  74. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  75. package/src/services/plan/plan-executor-context.ts +2 -0
  76. package/src/services/plan/plan-executor-graph.ts +366 -391
  77. package/src/services/plan/plan-executor.service.ts +13 -91
  78. package/src/services/plan/plan-scheduler.service.ts +62 -49
  79. package/src/services/plan/plan-transaction-events.ts +1 -1
  80. package/src/services/recent-activity-title.service.ts +6 -2
  81. package/src/services/thread/thread-bootstrap.ts +11 -9
  82. package/src/services/thread/thread-message.service.ts +6 -5
  83. package/src/services/thread/thread-turn-execution.ts +86 -82
  84. package/src/services/thread/thread-turn-preparation.service.ts +92 -45
  85. package/src/services/thread/thread-turn-streaming.ts +60 -28
  86. package/src/services/thread/thread-turn.ts +212 -46
  87. package/src/services/thread/thread.service.ts +21 -6
  88. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  89. package/src/system-agents/thread-router.agent.ts +23 -20
  90. package/src/tools/execution-plan.tool.ts +8 -3
  91. package/src/tools/fetch-webpage.tool.ts +10 -9
  92. package/src/tools/firecrawl-client.ts +0 -15
  93. package/src/tools/remember-memory.tool.ts +3 -6
  94. package/src/tools/research-topic.tool.ts +12 -3
  95. package/src/tools/search-web.tool.ts +10 -9
  96. package/src/tools/search.tool.ts +4 -5
  97. package/src/tools/team-think.tool.ts +139 -121
  98. package/src/workers/bootstrap.ts +9 -10
  99. package/src/workers/memory-consolidation.worker.ts +4 -1
  100. package/src/workers/organization-learning.worker.ts +15 -2
  101. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  102. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  103. package/src/workers/skill-extraction.runner.ts +13 -15
  104. package/src/workers/worker-utils.ts +6 -18
  105. package/src/effect/awaitable-effect.ts +0 -96
  106. package/src/effect/runtime-ref.ts +0 -25
  107. package/src/effect/runtime.ts +0 -46
  108. package/src/redis/runtime-connection.ts +0 -20
  109. package/src/runtime/runtime-accessors.ts +0 -92
  110. package/src/runtime/runtime-token.ts +0 -47
@@ -5,8 +5,10 @@ import type IORedis from 'ioredis'
5
5
 
6
6
  import { ensureRecordId } from '../db/record-id'
7
7
  import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
8
+ import { QueueJobServiceTag } from '../services/queue-job.service'
8
9
  import { RecentActivityTitleServiceTag } from '../services/recent-activity-title.service'
9
10
  import { ThreadTitleServiceTag } from '../services/thread/thread-title.service'
11
+ import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
10
12
  import { createQueueFactoryWithDeps } from './queue-factory'
11
13
  import { runStandaloneQueueWorker } from './standalone-worker'
12
14
 
@@ -29,13 +31,13 @@ interface RecentActivityTitleRefinementJob {
29
31
 
30
32
  type TitleGenerationJob = ThreadTitleGenerationJob | RecentActivityTitleRefinementJob
31
33
 
32
- interface TitleGenerationQueueDeps {
34
+ export interface TitleGenerationWorkerDeps {
33
35
  databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
34
36
  threadTitleService: Context.Service.Shape<typeof ThreadTitleServiceTag>
35
37
  recentActivityTitleService: Context.Service.Shape<typeof RecentActivityTitleServiceTag>
36
38
  }
37
39
 
38
- function processTitleGenerationJob(deps: TitleGenerationQueueDeps, job: Job<TitleGenerationJob>): Promise<void> {
40
+ function processTitleGenerationJob(deps: TitleGenerationWorkerDeps, job: Job<TitleGenerationJob>): Promise<void> {
39
41
  const { threadTitleService, recentActivityTitleService } = deps
40
42
  if (job.data.kind === 'thread-title') {
41
43
  return Effect.runPromise(
@@ -46,44 +48,53 @@ function processTitleGenerationJob(deps: TitleGenerationQueueDeps, job: Job<Titl
46
48
  return Effect.runPromise(Effect.asVoid(recentActivityTitleService.refineRecentActivityTitle(job.data.activityId)))
47
49
  }
48
50
 
49
- const titleGeneration = createQueueFactoryWithDeps<TitleGenerationJob, TitleGenerationQueueDeps>({
50
- name: TITLE_GENERATION_QUEUE,
51
- displayName: 'Title generation',
52
- jobName: 'title-generation',
53
- concurrency: 10,
54
- lockDuration: 300_000,
55
- defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
56
- prepare: ({ databaseService }) => databaseService.connect(),
57
- processor: processTitleGenerationJob,
58
- })
59
-
60
- export function enqueueThreadTitleGeneration(job: Omit<ThreadTitleGenerationJob, 'kind'>) {
61
- return titleGeneration.enqueue({ kind: 'thread-title', ...job }, { jobId: `thread-title:${job.threadId}` })
51
+ export interface TitleGenerationQueueRuntime {
52
+ enqueueThreadTitleGeneration(job: Omit<ThreadTitleGenerationJob, 'kind'>): Promise<void>
53
+ enqueueRecentActivityTitleRefinement(job: Omit<RecentActivityTitleRefinementJob, 'kind'>): Promise<void>
54
+ startWorker(options: { registerSignals?: boolean; deps: TitleGenerationWorkerDeps }): WorkerHandle
62
55
  }
63
56
 
64
- export function enqueueRecentActivityTitleRefinement(job: Omit<RecentActivityTitleRefinementJob, 'kind'>) {
65
- return titleGeneration.enqueue(
66
- { kind: 'recent-activity-title', ...job },
67
- { jobId: `recent-activity-title:${job.activityId}` },
68
- )
57
+ interface MakeTitleGenerationQueueRuntimeParams {
58
+ connectionProvider: () => IORedis
59
+ queueJobService: QueueJobService
69
60
  }
70
61
 
71
- export function startTitleGenerationWorker(options: {
72
- registerSignals?: boolean
73
- connectionProvider: () => IORedis
74
- deps: TitleGenerationQueueDeps
75
- }): ReturnType<typeof titleGeneration.startWorker> {
76
- return titleGeneration.startWorker({
77
- deps: options.deps,
78
- registerSignals: options.registerSignals,
79
- connectionProvider: options.connectionProvider,
62
+ export function makeTitleGenerationQueueRuntime(
63
+ params: MakeTitleGenerationQueueRuntimeParams,
64
+ ): TitleGenerationQueueRuntime {
65
+ const { connectionProvider, queueJobService } = params
66
+
67
+ const queue = createQueueFactoryWithDeps<TitleGenerationJob, TitleGenerationWorkerDeps>({
68
+ name: TITLE_GENERATION_QUEUE,
69
+ displayName: 'Title generation',
70
+ jobName: 'title-generation',
71
+ concurrency: 10,
72
+ lockDuration: 300_000,
73
+ defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
74
+ connectionProvider,
75
+ queueJobService,
76
+ prepare: ({ databaseService }) => Effect.runPromise(databaseService.connect()),
77
+ processor: processTitleGenerationJob,
80
78
  })
79
+
80
+ return {
81
+ enqueueThreadTitleGeneration: (job) =>
82
+ queue.enqueue({ kind: 'thread-title', ...job }, { jobId: `thread-title:${job.threadId}` }),
83
+ enqueueRecentActivityTitleRefinement: (job) =>
84
+ queue.enqueue({ kind: 'recent-activity-title', ...job }, { jobId: `recent-activity-title:${job.activityId}` }),
85
+ startWorker: (options) =>
86
+ queue.startWorker({ deps: options.deps, registerSignals: options.registerSignals, connectionProvider }),
87
+ }
81
88
  }
82
89
 
83
90
  runStandaloneQueueWorker((runtime) => {
84
91
  const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
85
- startTitleGenerationWorker({
86
- connectionProvider: () => resolve(RedisServiceTag).getConnectionForBullMQ(),
92
+ const redis = resolve(RedisServiceTag)
93
+ const titleGenerationQueue = makeTitleGenerationQueueRuntime({
94
+ connectionProvider: () => redis.getConnectionForBullMQ(),
95
+ queueJobService: resolve(QueueJobServiceTag),
96
+ })
97
+ titleGenerationQueue.startWorker({
87
98
  deps: {
88
99
  databaseService: resolve(DatabaseServiceTag),
89
100
  threadTitleService: resolve(ThreadTitleServiceTag),
@@ -1,12 +1,12 @@
1
1
  import { Schema, Effect } from 'effect'
2
- import type IORedis from 'ioredis'
3
2
 
4
3
  import { serverLogger } from '../config/logger'
5
4
  import { LockAcquisitionError } from '../effect/errors'
6
5
  import type { LockLostError, RedisError } from '../effect/errors'
6
+ import { RedisServiceTag } from '../effect/services'
7
+ import type { RedisConnectionManager } from './connection'
7
8
  import { withLeaseLock } from './redis-lease-lock'
8
9
  import type { RedisLeaseLockOptions } from './redis-lease-lock'
9
- import { getRuntimeRedisConnection } from './runtime-connection'
10
10
 
11
11
  const ORG_MEMORY_LOCK_PREFIX = 'lock:org-memory:org:'
12
12
  const ORG_MEMORY_LOCK_TTL_MS = 120_000
@@ -20,9 +20,8 @@ class OrgMemoryLockCallbackError extends Schema.TaggedErrorClass<OrgMemoryLockCa
20
20
  { message: Schema.String, cause: Schema.Defect },
21
21
  ) {}
22
22
 
23
- function createOrgMemoryLockOptions(redis: IORedis, orgId: string): RedisLeaseLockOptions & { redis: IORedis } {
23
+ function createOrgMemoryLockOptions(orgId: string): RedisLeaseLockOptions {
24
24
  return {
25
- redis,
26
25
  lockKey: `${ORG_MEMORY_LOCK_PREFIX}${orgId}`,
27
26
  lockTtlMs: ORG_MEMORY_LOCK_TTL_MS,
28
27
  retryDelayMs: ORG_MEMORY_LOCK_RETRY_DELAY_MS,
@@ -44,13 +43,28 @@ function createOrgMemoryLockOptions(redis: IORedis, orgId: string): RedisLeaseLo
44
43
  }
45
44
  }
46
45
 
47
- export function withOrgMemoryLock<T>(orgId: string, fn: (signal: AbortSignal) => Promise<T>): Promise<T> {
46
+ /**
47
+ * Promise-returning wrapper for hosts that live outside Effect.
48
+ *
49
+ * Callers must pass in a `RedisConnectionManager` (usually
50
+ * `runtime.redis.manager`) so the wrapper can build a standalone Redis layer
51
+ * instead of relying on an ambient managed runtime.
52
+ */
53
+ export function withOrgMemoryLock<T>(
54
+ orgId: string,
55
+ fn: (signal: AbortSignal) => Promise<T>,
56
+ redisManager: RedisConnectionManager,
57
+ ): Promise<T> {
48
58
  return Effect.runPromise(
49
59
  withOrgMemoryLockEffect(orgId, (signal) =>
50
60
  Effect.tryPromise({
51
61
  try: () => fn(signal),
52
62
  catch: (cause) => new OrgMemoryLockCallbackError({ message: 'Org memory lock callback failed.', cause }),
53
63
  }),
64
+ ).pipe(
65
+ // Provide the redis service synchronously so the Effect has no residual
66
+ // requirements; the outer Promise call edge needs `R = never`.
67
+ Effect.provideService(RedisServiceTag, redisManager),
54
68
  ),
55
69
  )
56
70
  }
@@ -58,7 +72,7 @@ export function withOrgMemoryLock<T>(orgId: string, fn: (signal: AbortSignal) =>
58
72
  export function withOrgMemoryLockEffect<A, E>(
59
73
  orgId: string,
60
74
  fn: (signal: AbortSignal) => Effect.Effect<A, E>,
61
- ): Effect.Effect<A, E | LockAcquisitionError | LockLostError | RedisError> {
75
+ ): Effect.Effect<A, E | LockAcquisitionError | LockLostError | RedisError, RedisServiceTag> {
62
76
  const normalizedOrgId = orgId.trim()
63
77
 
64
78
  if (!normalizedOrgId) {
@@ -70,7 +84,8 @@ export function withOrgMemoryLockEffect<A, E>(
70
84
  )
71
85
  }
72
86
 
73
- return Effect.sync(() => getRuntimeRedisConnection()).pipe(
74
- Effect.flatMap((redis) => withLeaseLock(createOrgMemoryLockOptions(redis, normalizedOrgId), fn)),
75
- )
87
+ return Effect.gen(function* () {
88
+ const redis = yield* RedisServiceTag
89
+ return yield* withLeaseLock({ ...createOrgMemoryLockOptions(normalizedOrgId), redis: redis.getConnection() }, fn)
90
+ })
76
91
  }
@@ -259,9 +259,16 @@ export function withLeaseLock<A, E, R>(
259
259
  Effect.ignore,
260
260
  )
261
261
 
262
+ // The refresh fiber races the user's effect against a `lockLost` Deferred
263
+ // so callers can observe Redis-side expirations. We pass the abort signal
264
+ // to `fn` so it can cooperatively wind down on lost-lock or external stop,
265
+ // but do NOT interrupt `fn` via `raceFirst` on `lockLost` — aborting a
266
+ // turn that has already produced tokens would discard the response. The
267
+ // caller owns the post-lease cleanup; the signal is enough for anyone who
268
+ // needs to bail out early.
262
269
  return yield* Effect.scoped(
263
270
  startRefreshFiber(redis, normalized, lockValue, abortController, lockLost).pipe(
264
- Effect.andThen(fn(abortController.signal).pipe(Effect.raceFirst(Deferred.await(lockLost)))),
271
+ Effect.andThen(fn(abortController.signal)),
265
272
  Effect.ensuring(cleanup),
266
273
  ),
267
274
  )
@@ -1,4 +1,4 @@
1
- import { getAgentDisplayNames } from '../config/agent-defaults'
1
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
2
2
  import { asRecord, readOptionalString } from './thread-chat-helpers'
3
3
 
4
4
  interface RuntimeAgentIdentityOverrides {
@@ -52,11 +52,15 @@ export function readRuntimeAgentIdentityOverrides(
52
52
  }
53
53
  }
54
54
 
55
- export function resolveRuntimeAgentDisplayName(overrides: RuntimeAgentIdentityOverrides, agentId: string): string {
55
+ export function resolveRuntimeAgentDisplayName(
56
+ agentConfig: ResolvedAgentConfig,
57
+ overrides: RuntimeAgentIdentityOverrides,
58
+ agentId: string,
59
+ ): string {
56
60
  const override = readStringOverride(overrides.displayNamesById, agentId)
57
61
  if (override !== undefined) {
58
62
  return override
59
63
  }
60
64
 
61
- return getAgentDisplayNames()[agentId] ?? agentId
65
+ return agentConfig.displayNames[agentId] ?? agentId
62
66
  }
@@ -7,7 +7,7 @@ import type {
7
7
  PlanNodeSpecRecord,
8
8
  } from '@lota-sdk/shared'
9
9
 
10
- import { getLeadAgentId } from '../config/agent-defaults'
10
+ import type { ResolvedThreadBootstrapConfig } from '../config/thread-defaults'
11
11
  import { resolveOnboardingOwnerAgentId } from '../config/thread-defaults'
12
12
  export interface AgentRuntimeConfig<TAgent extends string> {
13
13
  id: TAgent
@@ -162,13 +162,18 @@ export function buildThreadAgentToolPolicy<TAgent extends string, TSkill extends
162
162
  linearInstalled: boolean
163
163
  githubInstalled: boolean
164
164
  provideRepoTool: boolean
165
- leadAgentId?: TAgent
165
+ leadAgentId: TAgent
166
166
  onboardingOwnerAgentId?: TAgent
167
+ threadBootstrapConfig?: ResolvedThreadBootstrapConfig
167
168
  getAgentSkills: (agentId: TAgent, mode: ChatMode) => TSkill[]
168
169
  }): AgentToolPolicy<TSkill> {
169
170
  const resolvedMode = params.mode ?? toChatMode(params.threadType)
170
- const leadAgentId = params.leadAgentId ?? (getLeadAgentId() as TAgent)
171
- const onboardingOwnerAgentId = params.onboardingOwnerAgentId ?? (resolveOnboardingOwnerAgentId(leadAgentId) as TAgent)
171
+ const leadAgentId = params.leadAgentId
172
+ const onboardingOwnerAgentId =
173
+ params.onboardingOwnerAgentId ??
174
+ (params.threadBootstrapConfig
175
+ ? (resolveOnboardingOwnerAgentId(leadAgentId, params.threadBootstrapConfig) as TAgent)
176
+ : leadAgentId)
172
177
  const skills = resolveActiveAgentSkills({
173
178
  agentId: params.agentId,
174
179
  threadType: params.threadType,
@@ -2,7 +2,7 @@ import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import type { LanguageModelUsage, UIMessageStreamOptions } from 'ai'
3
3
  import { Duration, Effect, Exit, Scope } from 'effect'
4
4
 
5
- import { getAgentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
5
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
6
6
  import { nowEpochMillis } from '../utils/date-time'
7
7
  import { readRecord as _readRecord } from '../utils/string'
8
8
 
@@ -139,9 +139,14 @@ export function createTimedAbortSignal(parentSignal: AbortSignal, timeoutMs: num
139
139
  }
140
140
  }
141
141
 
142
- export function buildSpecialistTaskMessage(params: { agentId: string; task: string }): ChatMessage {
143
- const displayName = getAgentDisplayNames()[params.agentId] ?? params.agentId
144
- const leadAgentDisplayName = getLeadAgentDisplayName()
142
+ export function buildSpecialistTaskMessage(params: {
143
+ agentConfig: ResolvedAgentConfig
144
+ agentId: string
145
+ task: string
146
+ }): ChatMessage {
147
+ const displayName = params.agentConfig.displayNames[params.agentId] ?? params.agentId
148
+ const leadAgentDisplayName =
149
+ params.agentConfig.displayNames[params.agentConfig.leadAgentId] ?? params.agentConfig.leadAgentId
145
150
  return {
146
151
  id: Bun.randomUUIDv7(),
147
152
  role: 'user',
@@ -48,33 +48,31 @@ function tryContextCompactionPromise<A>(
48
48
  })
49
49
  }
50
50
 
51
- function runContextCompacter(helperModelRuntime: HelperModelRuntime, params: ContextCompactionRunnerParams) {
52
- return Effect.runPromise(
53
- tryContextCompactionPromise('Failed to compact runtime context.', () =>
54
- helperModelRuntime.generateHelperStructured({
55
- tag: 'context-compaction',
56
- createAgent: createContextCompactionAgent,
57
- messages: [
58
- {
59
- role: 'user',
60
- content: buildContextCompactionPrompt({
61
- previousSummary: params.previousSummary,
62
- transcript: params.transcript,
63
- }),
64
- },
65
- ],
66
- schema: ContextCompactionOutputSchema,
67
- maxOutputTokens: 8_000,
68
- }),
69
- ).pipe(Effect.flatMap(parseCompactionOutput)),
70
- )
51
+ function runContextCompacterEffect(helperModelRuntime: HelperModelRuntime, params: ContextCompactionRunnerParams) {
52
+ return tryContextCompactionPromise('Failed to compact runtime context.', () =>
53
+ helperModelRuntime.generateHelperStructured({
54
+ tag: 'context-compaction',
55
+ createAgent: createContextCompactionAgent,
56
+ messages: [
57
+ {
58
+ role: 'user',
59
+ content: buildContextCompactionPrompt({
60
+ previousSummary: params.previousSummary,
61
+ transcript: params.transcript,
62
+ }),
63
+ },
64
+ ],
65
+ schema: ContextCompactionOutputSchema,
66
+ maxOutputTokens: 8_000,
67
+ }),
68
+ ).pipe(Effect.flatMap(parseCompactionOutput))
71
69
  }
72
70
 
73
71
  export function createWiredContextCompactionRuntime(deps: CreateContextCompactionRuntimeDeps) {
74
72
  const { helperModelRuntime } = deps
75
73
 
76
74
  const runtime = createContextCompactionRuntime({
77
- runCompacter: (params) => runContextCompacter(helperModelRuntime, params),
75
+ runCompacter: (params) => runContextCompacterEffect(helperModelRuntime, params),
78
76
  now: deps.now,
79
77
  randomId: deps.randomId,
80
78
  thresholdRatio: CONTEXT_COMPACTION_THRESHOLD_RATIO,
@@ -86,20 +84,18 @@ export function createWiredContextCompactionRuntime(deps: CreateContextCompactio
86
84
  includedToolPrefixes: CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
87
85
  })
88
86
 
89
- function compactMemoryBlockSummary(params: { previousSummary: string; newEntriesText: string }): Promise<string> {
87
+ function compactMemoryBlockSummary(params: { previousSummary: string; newEntriesText: string }) {
90
88
  const previousSummary = params.previousSummary.trim()
91
89
  const newEntriesText = params.newEntriesText.trim()
92
- if (!previousSummary && !newEntriesText) return Promise.resolve('')
90
+ if (!previousSummary && !newEntriesText) return Effect.succeed('')
93
91
 
94
- return Effect.runPromise(
95
- tryContextCompactionPromise('Failed to compact memory block summary.', () =>
96
- helperModelRuntime.generateHelperText({
97
- tag: 'memory-block-compaction',
98
- createAgent: createContextCompactionAgent,
99
- messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
100
- maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
101
- }),
102
- ),
92
+ return tryContextCompactionPromise('Failed to compact memory block summary.', () =>
93
+ helperModelRuntime.generateHelperText({
94
+ tag: 'memory-block-compaction',
95
+ createAgent: createContextCompactionAgent,
96
+ messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
97
+ maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
98
+ }),
103
99
  )
104
100
  }
105
101
 
@@ -88,7 +88,9 @@ export interface CompactionOutput {
88
88
  summary: string
89
89
  }
90
90
 
91
- export type ContextCompactionRunner = (params: ContextCompactionRunnerParams) => Promise<CompactionOutput>
91
+ export type ContextCompactionRunner = (
92
+ params: ContextCompactionRunnerParams,
93
+ ) => Effect.Effect<CompactionOutput, unknown>
92
94
 
93
95
  export interface CreateContextCompactionRuntimeOptions {
94
96
  runCompacter: ContextCompactionRunner
@@ -112,7 +114,7 @@ export interface ContextCompactionRuntime {
112
114
  liveMessages: ChatMessage[]
113
115
  contextSize?: number
114
116
  }) => CompactionAssessment
115
- compactHistory: (params: CompactHistoryParams) => Promise<CompactHistoryResult>
117
+ compactHistory: (params: CompactHistoryParams) => Effect.Effect<CompactHistoryResult, CompactionError>
116
118
  }
117
119
 
118
120
  function estimateTokens(text: string): number {
@@ -348,10 +350,10 @@ export function createContextCompactionRuntime(
348
350
  Effect.gen(function* () {
349
351
  const chunk = chunks[state.index]
350
352
  const transcript = toCompactionTranscript(chunk)
351
- const output = yield* Effect.tryPromise({
352
- try: () => options.runCompacter({ previousSummary: state.summary, chunk, transcript }),
353
- catch: (error: unknown) => new CompactionError({ message: String(error), cause: error }),
354
- }).pipe(Effect.retry(COMPACTION_RUNNER_RETRY_OPTIONS))
353
+ const output = yield* options.runCompacter({ previousSummary: state.summary, chunk, transcript }).pipe(
354
+ Effect.mapError((error) => new CompactionError({ message: String(error), cause: error })),
355
+ Effect.retry(COMPACTION_RUNNER_RETRY_OPTIONS),
356
+ )
355
357
  return { summary: normalizeSummary(output.summary), index: state.index + 1 }
356
358
  }),
357
359
  },
@@ -479,7 +481,7 @@ export function createContextCompactionRuntime(
479
481
  prependSummaryMessage,
480
482
  estimateThreshold,
481
483
  shouldCompactHistory,
482
- compactHistory: (params) => Effect.runPromise(compactHistoryEffect(params)),
484
+ compactHistory: compactHistoryEffect,
483
485
  }
484
486
  }
485
487
 
@@ -13,6 +13,7 @@ import { Layer } from 'effect'
13
13
  import { AiGatewayLive } from '../ai-gateway/ai-gateway'
14
14
  import { EmbeddingCacheLive } from '../ai/embedding-cache'
15
15
  import type { buildInfrastructureLayer } from '../effect/layers'
16
+ import { LotaQueuesLive } from '../queues/queues.service'
16
17
  import { SharedThreadStreamSubscriberLive } from '../redis/stream-context'
17
18
  import { AgentActivityServiceLive } from '../services/agent-activity.service'
18
19
  import { AgentExecutorServiceLive } from '../services/agent-executor.service'
@@ -121,7 +122,6 @@ export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer
121
122
  OrganizationMemberServiceLive,
122
123
  PlanApprovalServiceLive,
123
124
  PlanRunServiceLive,
124
- PlanSchedulerServiceLive,
125
125
  PlanWorkspaceServiceLive,
126
126
  PluginExecutorServiceLive,
127
127
  QualityMetricsServiceLive,
@@ -135,7 +135,18 @@ export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer
135
135
  ),
136
136
  ctx0,
137
137
  )
138
- const ctx1 = Layer.mergeAll(ctx0, tier1)
138
+ const ctx1Base = Layer.mergeAll(ctx0, tier1)
139
+
140
+ // LotaQueuesLive depends only on Redis + QueueJobService from tier1/infra.
141
+ // It's threaded in before the tiers that need queue enqueue access so the
142
+ // service layers that enqueue jobs (AutonomousJobService, plan scheduler,
143
+ // heartbeat, etc.) can resolve LotaQueuesServiceTag cleanly.
144
+ const queuesTier = provide(LotaQueuesLive, ctx1Base)
145
+ const ctx1 = Layer.mergeAll(ctx1Base, queuesTier)
146
+ // PlanSchedulerServiceLive previously sat in tier1; it now depends on the
147
+ // queue layer, so it moves to the merged ctx1 context with queues available.
148
+ const planSchedulerTier = provide(PlanSchedulerServiceLive, ctx1)
149
+ const ctx1WithScheduler = Layer.mergeAll(ctx1, planSchedulerTier)
139
150
 
140
151
  const tier2 = provide(
141
152
  Layer.mergeAll(
@@ -154,9 +165,9 @@ export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer
154
165
  SkillResolverServiceLive,
155
166
  RecentActivityTitleServiceLive,
156
167
  ),
157
- ctx1,
168
+ ctx1WithScheduler,
158
169
  )
159
- const ctx2 = Layer.mergeAll(ctx1, tier2)
170
+ const ctx2 = Layer.mergeAll(ctx1WithScheduler, tier2)
160
171
 
161
172
  const tier3 = provide(Layer.mergeAll(PlanValidatorServiceLive, ThreadServiceLive), ctx2)
162
173
  const ctx3 = Layer.mergeAll(ctx2, tier3)
@@ -1,8 +1,10 @@
1
1
  import type { PlanExecutionVisibility, PlanNodeSpecRecord, PlanSpecRecord } from '@lota-sdk/shared'
2
2
 
3
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
3
4
  import { isAgentName } from '../config/agent-defaults'
4
5
 
5
6
  export function resolvePlanNodeExecutionVisibility(
7
+ agentConfig: ResolvedAgentConfig,
6
8
  plan: Pick<PlanSpecRecord, 'defaultExecutionVisibility'>,
7
9
  node: Pick<PlanNodeSpecRecord, 'executionVisibility' | 'owner'>,
8
10
  ): PlanExecutionVisibility {
@@ -12,12 +14,13 @@ export function resolvePlanNodeExecutionVisibility(
12
14
  return configuredVisibility
13
15
  }
14
16
 
15
- return node.owner.executorType === 'agent' && isAgentName(node.owner.ref) ? 'visible' : 'silent'
17
+ return node.owner.executorType === 'agent' && isAgentName(agentConfig, node.owner.ref) ? 'visible' : 'silent'
16
18
  }
17
19
 
18
20
  export function shouldPlanNodeUseVisibleTurn(
21
+ agentConfig: ResolvedAgentConfig,
19
22
  plan: Pick<PlanSpecRecord, 'defaultExecutionVisibility'>,
20
23
  node: Pick<PlanNodeSpecRecord, 'executionVisibility' | 'owner'>,
21
24
  ): boolean {
22
- return resolvePlanNodeExecutionVisibility(plan, node) === 'visible'
25
+ return resolvePlanNodeExecutionVisibility(agentConfig, plan, node) === 'visible'
23
26
  }
@@ -1,27 +1,5 @@
1
1
  import type { GraphDesignRequest, GraphDesignResponse } from '@lota-sdk/shared'
2
2
 
3
- import { resolveOptionalLotaService } from '../effect/runtime'
4
- import { RuntimeConfigServiceTag } from '../effect/services'
5
-
6
3
  export interface GraphDesigner {
7
4
  designGraph(request: GraphDesignRequest): Promise<GraphDesignResponse>
8
5
  }
9
-
10
- let currentGraphDesigner: GraphDesigner | null = null
11
-
12
- export function configureGraphDesigner(graphDesigner: GraphDesigner | null | undefined): void {
13
- currentGraphDesigner = graphDesigner ?? null
14
- }
15
-
16
- export function clearGraphDesigner(): void {
17
- currentGraphDesigner = null
18
- }
19
-
20
- export function getGraphDesigner(): GraphDesigner | null {
21
- if (currentGraphDesigner) {
22
- return currentGraphDesigner
23
- }
24
-
25
- const runtimeConfig = resolveOptionalLotaService(RuntimeConfigServiceTag)
26
- return runtimeConfig?.graphDesigner ?? null
27
- }
@@ -2,6 +2,7 @@ export * from './approval-continuation'
2
2
  export * from './agent-runtime-policy'
3
3
  export * from './agent-stream-helpers'
4
4
  export * from './chat-request-routing'
5
+ export * from './chat-run-orchestration'
5
6
  export * from './chat-run-registry'
6
7
  export * from './context-compaction/context-compaction'
7
8
  export * from './execution-plan'
@@ -12,6 +13,7 @@ export * from './instruction-sections'
12
13
  export * from './memory/memory-block'
13
14
  export * from './memory/memory-digest-policy'
14
15
  export * from './memory/memory-scope'
16
+ export * from './live-turn-trace'
15
17
  export * from './llm-content'
16
18
  export * from './plugin-resolution'
17
19
  export * from './plugin-types'
@@ -1,5 +1,3 @@
1
- import { getAgentRoster } from '../config/agent-defaults'
2
-
3
1
  export type IndexedRepoAgentName = string
4
2
 
5
3
  export const REPO_SECTION_NAMES = [
@@ -16,13 +14,11 @@ export type RepoSectionName = (typeof REPO_SECTION_NAMES)[number]
16
14
  const ALL_REPO_SECTIONS: RepoSectionName[] = [...REPO_SECTION_NAMES]
17
15
 
18
16
  export function buildDefaultRepoSectionsByAgent(
19
- roster: readonly IndexedRepoAgentName[] = getAgentRoster(),
17
+ roster: readonly IndexedRepoAgentName[],
20
18
  ): Record<IndexedRepoAgentName, RepoSectionName[]> {
21
19
  return Object.fromEntries(roster.map((agentId) => [agentId, [...ALL_REPO_SECTIONS]]))
22
20
  }
23
21
 
24
- export function emptyAgentContextMap(
25
- roster: readonly IndexedRepoAgentName[] = getAgentRoster(),
26
- ): Record<IndexedRepoAgentName, string> {
22
+ export function emptyAgentContextMap(roster: readonly IndexedRepoAgentName[]): Record<IndexedRepoAgentName, string> {
27
23
  return Object.fromEntries(roster.map((agentId) => [agentId, '']))
28
24
  }