@lota-sdk/core 0.4.8 → 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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,25 +1,108 @@
1
- const COMPACTION_POLL_INTERVAL_MS = 200
1
+ import { Cause, Context, Schema, Duration, Effect, Latch, Layer } from 'effect'
2
+
3
+ import { effectTryPromise } from '../effect/helpers'
4
+ import { nowEpochMillis } from '../utils/date-time'
5
+
6
+ const COMPACTION_WAIT_REFRESH_MS = 1_000
2
7
  const COMPACTION_MAX_WAIT_MS = 120_000
3
8
 
4
- export async function waitForCompactionIfNeeded<TEntity>(params: {
9
+ class WaitForCompactionError extends Schema.TaggedErrorClass<WaitForCompactionError>()('WaitForCompactionError', {
10
+ entityId: Schema.String,
11
+ entityLabel: Schema.String,
12
+ message: Schema.String,
13
+ cause: Schema.optional(Schema.Defect),
14
+ }) {}
15
+
16
+ function toWaitForCompactionError(params: {
5
17
  entityId: string
6
18
  entityLabel: string
7
- loadEntity: () => Promise<TEntity>
8
- isCompacting: (entity: TEntity) => boolean
9
- }): Promise<TEntity> {
10
- let entity = await params.loadEntity()
11
- if (!params.isCompacting(entity)) return entity
12
-
13
- const deadline = Date.now() + COMPACTION_MAX_WAIT_MS
14
- while (params.isCompacting(entity)) {
15
- if (Date.now() > deadline) {
16
- throw new Error(
17
- `${params.entityLabel} ${params.entityId} compaction did not complete within ${COMPACTION_MAX_WAIT_MS}ms`,
18
- )
19
- }
20
- await Bun.sleep(COMPACTION_POLL_INTERVAL_MS)
21
- entity = await params.loadEntity()
22
- }
19
+ cause: unknown
20
+ }): WaitForCompactionError {
21
+ return new WaitForCompactionError({
22
+ entityId: params.entityId,
23
+ entityLabel: params.entityLabel,
24
+ message: params.cause instanceof Error ? params.cause.message : String(params.cause),
25
+ cause: params.cause,
26
+ })
27
+ }
23
28
 
24
- return entity
29
+ interface CompactionCoordination {
30
+ readonly signal: (entityId: string, compacting: boolean) => Effect.Effect<void>
31
+ readonly waitIfNeeded: <TEntity>(params: {
32
+ entityId: string
33
+ entityLabel: string
34
+ loadEntity: () => PromiseLike<TEntity> | Effect.Effect<TEntity, unknown>
35
+ isCompacting: (entity: TEntity) => boolean
36
+ }) => Effect.Effect<TEntity, WaitForCompactionError>
25
37
  }
38
+
39
+ export class CompactionCoordinationTag extends Context.Service<CompactionCoordinationTag, CompactionCoordination>()(
40
+ '@lota-sdk/core/CompactionCoordination',
41
+ ) {}
42
+
43
+ export const CompactionCoordinationLive = Layer.effect(
44
+ CompactionCoordinationTag,
45
+ Effect.sync((): CompactionCoordination => {
46
+ const latches = new Map<string, Latch.Latch>()
47
+
48
+ function getLatch(entityId: string): Latch.Latch {
49
+ let latch = latches.get(entityId)
50
+ if (!latch) {
51
+ latch = Latch.makeUnsafe(true)
52
+ latches.set(entityId, latch)
53
+ }
54
+ return latch
55
+ }
56
+
57
+ return {
58
+ signal: (entityId, compacting) =>
59
+ Effect.sync(() => {
60
+ const latch = getLatch(entityId)
61
+ if (compacting) {
62
+ Latch.closeUnsafe(latch)
63
+ } else {
64
+ Latch.openUnsafe(latch)
65
+ }
66
+ }),
67
+
68
+ waitIfNeeded: Effect.fn('CompactionCoordination.waitIfNeeded')(function* <TEntity>(params: {
69
+ entityId: string
70
+ entityLabel: string
71
+ loadEntity: () => PromiseLike<TEntity> | Effect.Effect<TEntity, unknown>
72
+ isCompacting: (entity: TEntity) => boolean
73
+ }) {
74
+ const deadline = nowEpochMillis() + COMPACTION_MAX_WAIT_MS
75
+ const latch = getLatch(params.entityId)
76
+ let entity = yield* effectTryPromise(
77
+ () => params.loadEntity(),
78
+ (cause) => toWaitForCompactionError({ entityId: params.entityId, entityLabel: params.entityLabel, cause }),
79
+ )
80
+
81
+ while (params.isCompacting(entity)) {
82
+ Latch.closeUnsafe(latch)
83
+ if (nowEpochMillis() > deadline) {
84
+ return yield* new WaitForCompactionError({
85
+ entityId: params.entityId,
86
+ entityLabel: params.entityLabel,
87
+ message: `${params.entityLabel} ${params.entityId} compaction did not complete within ${COMPACTION_MAX_WAIT_MS}ms`,
88
+ })
89
+ }
90
+
91
+ const remainingMs = Math.max(0, deadline - nowEpochMillis())
92
+ const refreshWindowMs = Math.min(COMPACTION_WAIT_REFRESH_MS, remainingMs)
93
+ yield* latch.await.pipe(
94
+ Effect.timeout(Duration.millis(refreshWindowMs)),
95
+ Effect.catchIf(Cause.isTimeoutError, () => Effect.void),
96
+ )
97
+ entity = yield* effectTryPromise(
98
+ () => params.loadEntity(),
99
+ (cause) => toWaitForCompactionError({ entityId: params.entityId, entityLabel: params.entityLabel, cause }),
100
+ )
101
+ }
102
+
103
+ Latch.openUnsafe(latch)
104
+ return entity
105
+ }),
106
+ }
107
+ }),
108
+ )
@@ -1,5 +1,9 @@
1
+ import { Effect, Fiber, FiberMap } from 'effect'
2
+
1
3
  export class ChatRunRegistry {
2
- private controllers = new Map<string, AbortController>()
4
+ private readonly controllers = new Map<string, AbortController>()
5
+
6
+ constructor(private readonly trackedRuns: FiberMap.FiberMap<string>) {}
3
7
 
4
8
  has(runId: string): boolean {
5
9
  return this.controllers.has(runId)
@@ -16,9 +20,39 @@ export class ChatRunRegistry {
16
20
  stop(runId: string, reason?: unknown): boolean {
17
21
  const controller = this.controllers.get(runId)
18
22
  if (!controller) return false
19
-
20
23
  this.controllers.delete(runId)
21
24
  controller.abort(reason)
25
+ void Effect.runFork(FiberMap.remove(this.trackedRuns, runId))
22
26
  return true
23
27
  }
28
+
29
+ trackRunEffect<A, E, R>(
30
+ runId: string,
31
+ controller: AbortController,
32
+ effect: Effect.Effect<A, E, R>,
33
+ ): Effect.Effect<A, E, R> {
34
+ return Effect.gen(
35
+ function* (this: ChatRunRegistry) {
36
+ this.controllers.set(runId, controller)
37
+ const fiber = yield* FiberMap.run(
38
+ this.trackedRuns,
39
+ runId,
40
+ effect.pipe(
41
+ Effect.ensuring(
42
+ Effect.sync(() => {
43
+ if (this.controllers.get(runId) === controller) {
44
+ this.controllers.delete(runId)
45
+ }
46
+ }),
47
+ ),
48
+ ),
49
+ )
50
+ return yield* Fiber.join(fiber)
51
+ }.bind(this),
52
+ )
53
+ }
54
+
55
+ stopEffect(runId: string, reason?: unknown): Effect.Effect<boolean> {
56
+ return Effect.sync(() => this.stop(runId, reason))
57
+ }
24
58
  }
@@ -0,0 +1,107 @@
1
+ import { Schema, Effect } from 'effect'
2
+
3
+ import { createContextCompactionAgent } from '../../system-agents/context-compaction.agent'
4
+ import type { GenerateHelperStructuredParams, GenerateHelperTextParams } from '../helper-model'
5
+ import {
6
+ buildContextCompactionPrompt,
7
+ buildMemoryBlockCompactionPrompt,
8
+ ContextCompactionOutputSchema,
9
+ createContextCompactionRuntime,
10
+ parseCompactionOutput,
11
+ } from './context-compaction'
12
+ import type { ContextCompactionRunnerParams } from './context-compaction'
13
+ import {
14
+ COMPACTION_CHUNK_MAX_CHARS,
15
+ CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
16
+ CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
17
+ CONTEXT_COMPACTION_THRESHOLD_RATIO,
18
+ CONTEXT_OUTPUT_RESERVE_TOKENS,
19
+ CONTEXT_SAFETY_MARGIN_TOKENS,
20
+ SUMMARY_ROLLUP_MAX_TOKENS,
21
+ } from './context-compaction-constants'
22
+
23
+ const CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS = 512
24
+
25
+ interface HelperModelRuntime {
26
+ generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T>
27
+ generateHelperText(params: GenerateHelperTextParams): Promise<string>
28
+ }
29
+
30
+ interface CreateContextCompactionRuntimeDeps {
31
+ helperModelRuntime: HelperModelRuntime
32
+ now?: () => number
33
+ randomId?: () => string
34
+ }
35
+
36
+ class ContextCompactionRuntimeError extends Schema.TaggedErrorClass<ContextCompactionRuntimeError>()(
37
+ 'ContextCompactionRuntimeError',
38
+ { message: Schema.String, cause: Schema.Defect },
39
+ ) {}
40
+
41
+ function tryContextCompactionPromise<A>(
42
+ message: string,
43
+ thunk: () => PromiseLike<A>,
44
+ ): Effect.Effect<A, ContextCompactionRuntimeError> {
45
+ return Effect.tryPromise({
46
+ try: () => Promise.resolve(thunk()),
47
+ catch: (cause) => new ContextCompactionRuntimeError({ message, cause }),
48
+ })
49
+ }
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
+ )
71
+ }
72
+
73
+ export function createWiredContextCompactionRuntime(deps: CreateContextCompactionRuntimeDeps) {
74
+ const { helperModelRuntime } = deps
75
+
76
+ const runtime = createContextCompactionRuntime({
77
+ runCompacter: (params) => runContextCompacter(helperModelRuntime, params),
78
+ now: deps.now,
79
+ randomId: deps.randomId,
80
+ thresholdRatio: CONTEXT_COMPACTION_THRESHOLD_RATIO,
81
+ outputReserveTokens: CONTEXT_OUTPUT_RESERVE_TOKENS,
82
+ safetyMarginTokens: CONTEXT_SAFETY_MARGIN_TOKENS,
83
+ compactionChunkMaxChars: COMPACTION_CHUNK_MAX_CHARS,
84
+ summaryRollupMaxTokens: SUMMARY_ROLLUP_MAX_TOKENS,
85
+ includedToolNames: CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
86
+ includedToolPrefixes: CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
87
+ })
88
+
89
+ function compactMemoryBlockSummary(params: { previousSummary: string; newEntriesText: string }): Promise<string> {
90
+ const previousSummary = params.previousSummary.trim()
91
+ const newEntriesText = params.newEntriesText.trim()
92
+ if (!previousSummary && !newEntriesText) return Promise.resolve('')
93
+
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
+ ),
103
+ )
104
+ }
105
+
106
+ return { ...runtime, compactMemoryBlockSummary }
107
+ }
@@ -1,7 +1,16 @@
1
1
  import type { ChatMessage } from '@lota-sdk/shared'
2
+ import { Duration, Effect, Schedule, Schema } from 'effect'
2
3
  import { z } from 'zod'
3
4
 
4
- import { CHARS_PER_TOKEN_ESTIMATE, compactWhitespace, readRecord, readString, stringifyUnknown } from '../utils/string'
5
+ import { iterateEffect } from '../../effect/helpers'
6
+ import { nowEpochMillis } from '../../utils/date-time'
7
+ import {
8
+ CHARS_PER_TOKEN_ESTIMATE,
9
+ compactWhitespace,
10
+ readRecord,
11
+ readString,
12
+ stringifyUnknown,
13
+ } from '../../utils/string'
5
14
  import {
6
15
  COMPACTION_CHUNK_MAX_CHARS,
7
16
  CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
@@ -51,6 +60,18 @@ export interface ContextCompactionRunnerParams {
51
60
  transcript: string
52
61
  }
53
62
 
63
+ class CompactionError extends Schema.TaggedErrorClass<CompactionError>()('CompactionError', {
64
+ message: Schema.String,
65
+ cause: Schema.optional(Schema.Defect),
66
+ }) {}
67
+
68
+ class CompactionParseError extends Schema.TaggedErrorClass<CompactionParseError>()('CompactionParseError', {
69
+ message: Schema.String,
70
+ cause: Schema.optional(Schema.Defect),
71
+ }) {}
72
+
73
+ const COMPACTION_RUNNER_RETRY_OPTIONS = { times: 2, schedule: Schedule.exponential(Duration.millis(500), 2) } as const
74
+
54
75
  export interface ContextCompactionPromptParams {
55
76
  previousSummary: string
56
77
  transcript: string
@@ -216,8 +237,8 @@ export function buildMemoryBlockCompactionPrompt(params: MemoryBlockCompactionPr
216
237
  export function createContextCompactionRuntime(
217
238
  options: CreateContextCompactionRuntimeOptions,
218
239
  ): ContextCompactionRuntime {
219
- const now = options.now ?? (() => Date.now())
220
- const randomId = options.randomId ?? (() => crypto.randomUUID())
240
+ const now = options.now ?? nowEpochMillis
241
+ const randomId = options.randomId ?? (() => Bun.randomUUIDv7())
221
242
  const thresholdRatio = options.thresholdRatio ?? CONTEXT_COMPACTION_THRESHOLD_RATIO
222
243
  const outputReserveTokens = options.outputReserveTokens ?? CONTEXT_OUTPUT_RESERVE_TOKENS
223
244
  const safetyMarginTokens = options.safetyMarginTokens ?? CONTEXT_SAFETY_MARGIN_TOKENS
@@ -312,114 +333,160 @@ export function createContextCompactionRuntime(
312
333
  return { estimatedTokens, threshold, shouldCompact: estimatedTokens >= threshold }
313
334
  }
314
335
 
315
- const compactContextMessages = async (params: {
336
+ const compactContextMessagesEffect = Effect.fn('ContextCompaction.compactContextMessages')(function* (params: {
316
337
  previousSummary: string
317
338
  newMessages: ContextMessage[]
318
- }): Promise<CompactionOutput> => {
339
+ }) {
319
340
  const chunks = splitByCharBudget(params.newMessages, compactionChunkMaxChars)
320
- let summary = normalizeSummary(params.previousSummary)
321
-
322
- for (const chunk of chunks) {
323
- const transcript = toCompactionTranscript(chunk)
324
- const output = await options.runCompacter({ previousSummary: summary, chunk, transcript })
325
- summary = normalizeSummary(output.summary)
326
- }
341
+ const initialSummary = normalizeSummary(params.previousSummary)
342
+
343
+ const finalSummary = yield* iterateEffect<{ summary: string; index: number }, CompactionError, never>(
344
+ { summary: initialSummary, index: 0 },
345
+ {
346
+ while: (state) => state.index < chunks.length,
347
+ body: (state) =>
348
+ Effect.gen(function* () {
349
+ const chunk = chunks[state.index]
350
+ 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))
355
+ return { summary: normalizeSummary(output.summary), index: state.index + 1 }
356
+ }),
357
+ },
358
+ )
359
+
360
+ return { summary: finalSummary.summary }
361
+ })
327
362
 
328
- return { summary }
329
- }
363
+ const rollupSummaryIfOversizedEffect = (summary: string) =>
364
+ Effect.gen(function* () {
365
+ if (estimateTokens(summary) <= summaryRollupMaxTokens) {
366
+ return summary
367
+ }
330
368
 
331
- const rollupSummaryIfOversized = async (summary: string): Promise<string> => {
332
- if (estimateTokens(summary) <= summaryRollupMaxTokens) {
333
- return summary
334
- }
369
+ const output = yield* compactContextMessagesEffect({
370
+ previousSummary: '',
371
+ newMessages: [{ role: 'assistant', text: summary, sourceMessageId: 'summary-rollup' }],
372
+ })
335
373
 
336
- const output = await compactContextMessages({
337
- previousSummary: '',
338
- newMessages: [{ role: 'assistant', text: summary, sourceMessageId: 'summary-rollup' }],
374
+ return normalizeSummary(output.summary)
339
375
  })
340
376
 
341
- return normalizeSummary(output.summary)
377
+ type CompactionLoopState = {
378
+ summaryText: string
379
+ remainingMessages: ChatMessage[]
380
+ compactedMessages: ChatMessage[]
381
+ lastCompactedMessageId: string | undefined
382
+ estimatedTokens: number
383
+ done: boolean
342
384
  }
343
385
 
344
- const compactHistory = async (params: CompactHistoryParams): Promise<CompactHistoryResult> => {
345
- let summaryText = normalizeSummary(params.summaryText)
346
- let remainingMessages = [...params.liveMessages]
347
- let compactedMessages: ChatMessage[] = []
348
- let lastCompactedMessageId: string | undefined
349
- const summaryPayload = buildSyntheticSummaryPayload(summaryText)
350
- const initialPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
351
- const inputChars = initialPayload.length
352
-
353
- const buildEarlyExitResult = (estimatedTokens: number): CompactHistoryResult => {
354
- const exitSummaryPayload = buildSyntheticSummaryPayload(summaryText)
355
- const outputPayload = JSON.stringify([...(exitSummaryPayload ? [exitSummaryPayload] : []), ...remainingMessages])
356
- return {
357
- compacted: compactedMessages.length > 0,
358
- summaryText,
359
- ...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
360
- compactedMessages,
361
- compactedMessageCount: compactedMessages.length,
362
- remainingMessageCount: remainingMessages.length,
363
- estimatedTokens,
364
- inputChars,
365
- outputChars: outputPayload.length,
366
- }
367
- }
368
-
369
- for (;;) {
370
- const assessment = shouldCompactHistory({
371
- summaryText,
372
- liveMessages: remainingMessages,
373
- contextSize: params.contextSize,
374
- })
375
-
376
- if (!assessment.shouldCompact) {
377
- return buildEarlyExitResult(assessment.estimatedTokens)
378
- }
379
-
380
- const boundary = Math.max(0, remainingMessages.length - params.tailMessageCount)
381
- if (boundary <= 0) {
382
- return buildEarlyExitResult(assessment.estimatedTokens)
386
+ const compactHistoryEffect = (params: CompactHistoryParams) =>
387
+ Effect.gen(function* () {
388
+ const initialSummaryText = normalizeSummary(params.summaryText)
389
+ const initialRemaining = [...params.liveMessages]
390
+ const summaryPayload = buildSyntheticSummaryPayload(initialSummaryText)
391
+ const initialPayload = stringifyUnknown([...(summaryPayload ? [summaryPayload] : []), ...initialRemaining]) ?? ''
392
+ const inputChars = initialPayload.length
393
+
394
+ const buildExitResult = (state: CompactionLoopState): CompactHistoryResult => {
395
+ const exitSummaryPayload = buildSyntheticSummaryPayload(state.summaryText)
396
+ const outputPayload =
397
+ stringifyUnknown([...(exitSummaryPayload ? [exitSummaryPayload] : []), ...state.remainingMessages]) ?? ''
398
+ return {
399
+ compacted: state.compactedMessages.length > 0,
400
+ summaryText: state.summaryText,
401
+ ...(state.lastCompactedMessageId ? { lastCompactedMessageId: state.lastCompactedMessageId } : {}),
402
+ compactedMessages: state.compactedMessages,
403
+ compactedMessageCount: state.compactedMessages.length,
404
+ remainingMessageCount: state.remainingMessages.length,
405
+ estimatedTokens: state.estimatedTokens,
406
+ inputChars,
407
+ outputChars: outputPayload.length,
408
+ }
383
409
  }
384
410
 
385
- const candidatePrefix = remainingMessages.slice(0, boundary)
386
- const messagesToCompact = candidatePrefix.filter((message) => !readIsCompacted(message))
387
- const contextMessages = messagesToCompact
388
- .map(toContextMessageFromChatMessage)
389
- .filter((message) => compactWhitespace(message.text).length > 0)
390
- const sourceText = toCompactionTranscript(contextMessages)
391
-
392
- if (!compactWhitespace(sourceText)) {
393
- return buildEarlyExitResult(assessment.estimatedTokens)
411
+ const initialState: CompactionLoopState = {
412
+ summaryText: initialSummaryText,
413
+ remainingMessages: initialRemaining,
414
+ compactedMessages: [],
415
+ lastCompactedMessageId: undefined,
416
+ estimatedTokens: 0,
417
+ done: false,
394
418
  }
395
419
 
396
- let nextSummary = normalizeSummary(
397
- (await compactContextMessages({ previousSummary: summaryText, newMessages: contextMessages })).summary,
398
- )
399
- nextSummary = await rollupSummaryIfOversized(nextSummary)
400
-
401
- if (nextSummary.length >= sourceText.length) {
402
- throw new Error('Compaction summary is not shorter than compacted source')
403
- }
420
+ const finalState = yield* iterateEffect<CompactionLoopState, CompactionError, never>(initialState, {
421
+ while: (state) => !state.done,
422
+ body: (state) =>
423
+ Effect.gen(function* () {
424
+ const assessment = shouldCompactHistory({
425
+ summaryText: state.summaryText,
426
+ liveMessages: state.remainingMessages,
427
+ contextSize: params.contextSize,
428
+ })
429
+
430
+ if (!assessment.shouldCompact) {
431
+ return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
432
+ }
433
+
434
+ const boundary = Math.max(0, state.remainingMessages.length - params.tailMessageCount)
435
+ if (boundary <= 0) {
436
+ return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
437
+ }
438
+
439
+ const candidatePrefix = state.remainingMessages.slice(0, boundary)
440
+ const messagesToCompact = candidatePrefix.filter((message) => !readIsCompacted(message))
441
+ const contextMessages = messagesToCompact
442
+ .map(toContextMessageFromChatMessage)
443
+ .filter((message) => compactWhitespace(message.text).length > 0)
444
+ const sourceText = toCompactionTranscript(contextMessages)
445
+
446
+ if (!compactWhitespace(sourceText)) {
447
+ return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
448
+ }
449
+
450
+ const compacted = yield* compactContextMessagesEffect({
451
+ previousSummary: state.summaryText,
452
+ newMessages: contextMessages,
453
+ })
454
+ const rolledSummary = yield* rollupSummaryIfOversizedEffect(normalizeSummary(compacted.summary))
455
+
456
+ if (rolledSummary.length >= sourceText.length) {
457
+ return yield* new CompactionError({ message: 'Compaction summary is not shorter than compacted source' })
458
+ }
459
+
460
+ return {
461
+ summaryText: rolledSummary,
462
+ remainingMessages: state.remainingMessages.slice(boundary),
463
+ compactedMessages: [
464
+ ...state.compactedMessages,
465
+ ...candidatePrefix.map((message) => markMessageCompacted(message, now)),
466
+ ],
467
+ lastCompactedMessageId: candidatePrefix.at(-1)?.id ?? state.lastCompactedMessageId,
468
+ estimatedTokens: assessment.estimatedTokens,
469
+ done: false,
470
+ }
471
+ }),
472
+ })
404
473
 
405
- summaryText = nextSummary
406
- compactedMessages = [
407
- ...compactedMessages,
408
- ...candidatePrefix.map((message) => markMessageCompacted(message, now)),
409
- ]
410
- lastCompactedMessageId = candidatePrefix.at(-1)?.id ?? lastCompactedMessageId
411
- remainingMessages = remainingMessages.slice(boundary)
474
+ return buildExitResult(finalState)
475
+ })
412
476
 
413
- if (remainingMessages.length <= params.tailMessageCount) {
414
- continue
415
- }
416
- }
477
+ return {
478
+ createSummaryMessage,
479
+ prependSummaryMessage,
480
+ estimateThreshold,
481
+ shouldCompactHistory,
482
+ compactHistory: (params) => Effect.runPromise(compactHistoryEffect(params)),
417
483
  }
418
-
419
- return { createSummaryMessage, prependSummaryMessage, estimateThreshold, shouldCompactHistory, compactHistory }
420
484
  }
421
485
 
422
- export function parseCompactionOutput(value: unknown): CompactionOutput {
423
- const parsed = ContextCompactionOutputSchema.parse(value)
424
- return { summary: parsed.summary }
486
+ export function parseCompactionOutput(value: unknown): Effect.Effect<CompactionOutput, CompactionParseError> {
487
+ const parsed = ContextCompactionOutputSchema.safeParse(value)
488
+ if (!parsed.success) {
489
+ return Effect.fail(new CompactionParseError({ message: 'Failed to parse compaction output', cause: parsed.error }))
490
+ }
491
+ return Effect.succeed({ summary: parsed.data.summary })
425
492
  }