@lota-sdk/core 0.4.7 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,18 +1,67 @@
1
1
  import type { SandboxedJob } from 'bullmq'
2
+ import { Schema, Effect } from 'effect'
2
3
  import { BoundQuery, eq, inside } from 'surrealdb'
3
4
 
4
5
  import { MEMORY } from '../config/constants'
5
6
  import { serverLogger } from '../config/logger'
6
7
  import { ensureRecordId, recordIdToString } from '../db/record-id'
7
8
  import type { RecordIdInput } from '../db/record-id'
8
- import { databaseService } from '../db/service'
9
+ import type { SurrealDBService } from '../db/service'
9
10
  import { TABLES } from '../db/tables'
11
+ import { effectTryPromise } from '../effect/helpers'
12
+ import { DatabaseServiceTag } from '../effect/services'
10
13
  import type { MemoryConsolidationJob } from '../queues/memory-consolidation.queue'
14
+ import { nowDate, unsafeDateFrom } from '../utils/date-time'
15
+ import { getErrorMessage } from '../utils/errors'
11
16
  import { initializeSandboxedWorkerRuntime } from './bootstrap'
12
17
  import { toSandboxedWorkerError } from './utils/sandbox-error'
13
18
  import { createTracedWorkerProcessor } from './worker-utils'
14
19
 
15
- await initializeSandboxedWorkerRuntime()
20
+ class MemoryConsolidationError extends Schema.TaggedErrorClass<MemoryConsolidationError>()('MemoryConsolidationError', {
21
+ stage: Schema.Literals([
22
+ 'read-memories',
23
+ 'read-neighbors',
24
+ 'archive-memory',
25
+ 'relate-memories',
26
+ 'write-history',
27
+ 'load-stale',
28
+ 'update-stale',
29
+ 'load-middle-nodes',
30
+ 'read-existing-relation',
31
+ 'write-relation',
32
+ 'update-middle-node',
33
+ 'decay-standard',
34
+ 'decay-ephemeral',
35
+ 'cleanup-relations',
36
+ 'load-scope-ids',
37
+ ]),
38
+ message: Schema.String,
39
+ cause: Schema.Defect,
40
+ }) {}
41
+
42
+ function toMemoryConsolidationError(
43
+ stage: MemoryConsolidationError['stage'],
44
+ cause: unknown,
45
+ ): MemoryConsolidationError {
46
+ return new MemoryConsolidationError({ stage, message: getErrorMessage(cause), cause })
47
+ }
48
+
49
+ function tryMemoryConsolidationEffect<A, R = never>(
50
+ stage: MemoryConsolidationError['stage'],
51
+ thunk: () => PromiseLike<A> | Effect.Effect<A, unknown, R>,
52
+ ): Effect.Effect<A, MemoryConsolidationError, R> {
53
+ return effectTryPromise(thunk, (cause) => toMemoryConsolidationError(stage, cause))
54
+ }
55
+
56
+ const runtime = await initializeSandboxedWorkerRuntime()
57
+
58
+ const memoryConsolidationDatabaseService: SurrealDBService = await runtime.runPromise(
59
+ Effect.service(DatabaseServiceTag),
60
+ )
61
+
62
+ function db(): SurrealDBService {
63
+ return memoryConsolidationDatabaseService
64
+ }
16
65
 
17
66
  const MEMORY_TABLE = TABLES.MEMORY
18
67
  const MEMORY_RELATION_TABLE = TABLES.MEMORY_RELATION
@@ -39,272 +88,311 @@ function isContentSubsumed(shorter: string, longer: string): boolean {
39
88
  return aWords.size > 0 && overlap / aWords.size >= 0.8
40
89
  }
41
90
 
42
- async function deduplicateScope(scopeId: string): Promise<number> {
43
- const memoryRows = await databaseService.query<{
44
- id: RecordIdInput
45
- content: string
46
- importance: number
47
- embedding: number[]
48
- updatedAt?: string | Date | number | null
49
- createdAt: string | Date | number
50
- }>(
51
- new BoundQuery(
52
- `SELECT id, content, importance, embedding, updatedAt, createdAt
53
- FROM ${MEMORY_TABLE}
54
- WHERE scopeId = $scopeId AND archivedAt IS NONE
55
- ORDER BY createdAt DESC
56
- LIMIT $limit`,
57
- { scopeId, limit: MAX_MEMORIES_PER_SCOPE },
58
- ),
59
- )
60
-
61
- const memories = memoryRows.map((row) => ({ ...row, id: toMemoryId(row.id) }))
62
- if (memories.length < 2) return 0
63
-
64
- const archived = new Set<string>()
65
- let mergeCount = 0
66
-
67
- for (const memory of memories) {
68
- if (archived.has(memory.id)) continue
69
-
70
- const candidateLimit = 20
71
- const neighborStatements = await databaseService.queryAll<{
72
- id: RecordIdInput
73
- content: string
74
- importance: number
75
- similarity: number
76
- updatedAt?: string | Date | number | null
77
- createdAt: string | Date | number
78
- }>(
79
- new BoundQuery(
80
- `LET $candidateRows = (
81
- SELECT
82
- id,
83
- vector::distance::knn() AS distance
84
- FROM ${MEMORY_TABLE}
85
- WHERE scopeId = $scopeId
86
- AND archivedAt IS NONE
87
- AND id != $memoryId
88
- AND embedding <|${candidateLimit}|> $embedding
89
- ORDER BY distance ASC
90
- LIMIT ${candidateLimit}
91
- );
92
-
93
- SELECT
94
- id,
95
- content,
96
- importance,
97
- updatedAt,
98
- createdAt,
99
- vector::similarity::cosine(embedding, $embedding) AS similarity
100
- FROM ${MEMORY_TABLE}
101
- WHERE id IN $candidateRows.id
102
- ORDER BY similarity DESC
103
- LIMIT ${candidateLimit}`,
104
- { scopeId, memoryId: ensureRecordId(memory.id, TABLES.MEMORY), embedding: memory.embedding },
91
+ function deduplicateScopeEffect(scopeId: string) {
92
+ return Effect.gen(function* () {
93
+ const memoryRows = yield* tryMemoryConsolidationEffect('read-memories', () =>
94
+ db().query<{
95
+ id: RecordIdInput
96
+ content: string
97
+ importance: number
98
+ embedding: number[]
99
+ updatedAt?: string | Date | number | null
100
+ createdAt: string | Date | number
101
+ }>(
102
+ new BoundQuery(
103
+ `SELECT id, content, importance, embedding, updatedAt, createdAt
104
+ FROM ${MEMORY_TABLE}
105
+ WHERE scopeId = $scopeId AND archivedAt IS NONE
106
+ ORDER BY createdAt DESC
107
+ LIMIT $limit`,
108
+ { scopeId, limit: MAX_MEMORIES_PER_SCOPE },
109
+ ),
105
110
  ),
106
111
  )
107
112
 
108
- const neighbors = (neighborStatements.at(-1) ?? []).map((row) => ({ ...row, id: toMemoryId(row.id) }))
113
+ const memories = memoryRows.map((row) => ({ ...row, id: toMemoryId(row.id) }))
114
+ if (memories.length < 2) return 0
115
+
116
+ const archived = new Set<string>()
117
+ let mergeCount = 0
118
+
119
+ for (const memory of memories) {
120
+ if (archived.has(memory.id)) continue
121
+
122
+ const candidateLimit = 20
123
+ const neighborStatements = yield* tryMemoryConsolidationEffect('read-neighbors', () =>
124
+ db().queryAll<{
125
+ id: RecordIdInput
126
+ content: string
127
+ importance: number
128
+ similarity: number
129
+ updatedAt?: string | Date | number | null
130
+ createdAt: string | Date | number
131
+ }>(
132
+ new BoundQuery(
133
+ `LET $candidateRows = (
134
+ SELECT
135
+ id,
136
+ vector::distance::knn() AS distance
137
+ FROM ${MEMORY_TABLE}
138
+ WHERE scopeId = $scopeId
139
+ AND archivedAt IS NONE
140
+ AND id != $memoryId
141
+ AND embedding <|${candidateLimit}|> $embedding
142
+ ORDER BY distance ASC
143
+ LIMIT ${candidateLimit}
144
+ );
145
+
146
+ SELECT
147
+ id,
148
+ content,
149
+ importance,
150
+ updatedAt,
151
+ createdAt,
152
+ vector::similarity::cosine(embedding, $embedding) AS similarity
153
+ FROM ${MEMORY_TABLE}
154
+ WHERE id IN $candidateRows.id
155
+ ORDER BY similarity DESC
156
+ LIMIT ${candidateLimit}`,
157
+ { scopeId, memoryId: ensureRecordId(memory.id, TABLES.MEMORY), embedding: memory.embedding },
158
+ ),
159
+ ),
160
+ )
109
161
 
110
- for (const neighbor of neighbors) {
111
- if (neighbor.similarity < SOFT_SIMILARITY_THRESHOLD) break
112
- if (archived.has(neighbor.id)) continue
162
+ const neighbors = (neighborStatements.at(-1) ?? []).map((row) => ({ ...row, id: toMemoryId(row.id) }))
113
163
 
114
- const isHardMatch = neighbor.similarity >= HARD_SIMILARITY_THRESHOLD
115
- if (!isHardMatch) {
116
- const [shorter, longer] =
117
- memory.content.length <= neighbor.content.length
118
- ? [memory.content, neighbor.content]
119
- : [neighbor.content, memory.content]
120
- if (!isContentSubsumed(shorter, longer)) continue
121
- }
164
+ for (const neighbor of neighbors) {
165
+ if (neighbor.similarity < SOFT_SIMILARITY_THRESHOLD) break
166
+ if (archived.has(neighbor.id)) continue
122
167
 
123
- const memoryCreatedAt = new Date(memory.createdAt).getTime()
124
- const neighborCreatedAt = new Date(neighbor.createdAt).getTime()
125
- const keepMemory =
126
- memory.importance > neighbor.importance ||
127
- (memory.importance === neighbor.importance && memoryCreatedAt >= neighborCreatedAt)
128
- const winner = keepMemory ? memory : neighbor
129
- const loser = keepMemory ? neighbor : memory
130
-
131
- await databaseService.relate(
132
- ensureRecordId(winner.id, TABLES.MEMORY),
133
- MEMORY_RELATION_TABLE,
134
- ensureRecordId(loser.id, TABLES.MEMORY),
135
- { relationType: RELATION_SUPERSEDES, confidence: 1.0 },
136
- )
137
-
138
- await databaseService.query(
139
- new BoundQuery(
140
- `UPDATE ${MEMORY_TABLE}
141
- SET archivedAt = time::now(), validUntil = time::now()
142
- WHERE id = $loserId AND archivedAt IS NONE`,
143
- { loserId: ensureRecordId(loser.id, TABLES.MEMORY) },
144
- ),
145
- )
168
+ const isHardMatch = neighbor.similarity >= HARD_SIMILARITY_THRESHOLD
169
+ if (!isHardMatch) {
170
+ const [shorter, longer] =
171
+ memory.content.length <= neighbor.content.length
172
+ ? [memory.content, neighbor.content]
173
+ : [neighbor.content, memory.content]
174
+ if (!isContentSubsumed(shorter, longer)) continue
175
+ }
146
176
 
147
- await databaseService.insert<Record<string, unknown>>(MEMORY_HISTORY_TABLE, {
148
- memoryId: ensureRecordId(loser.id, TABLES.MEMORY),
149
- prevValue: loser.content,
150
- event: 'DELETE',
151
- })
177
+ const memoryCreatedAt = unsafeDateFrom(memory.createdAt).getTime()
178
+ const neighborCreatedAt = unsafeDateFrom(neighbor.createdAt).getTime()
179
+ const keepMemory =
180
+ memory.importance > neighbor.importance ||
181
+ (memory.importance === neighbor.importance && memoryCreatedAt >= neighborCreatedAt)
182
+ const winner = keepMemory ? memory : neighbor
183
+ const loser = keepMemory ? neighbor : memory
184
+
185
+ yield* tryMemoryConsolidationEffect('archive-memory', () =>
186
+ db().query(
187
+ new BoundQuery(
188
+ `UPDATE ${MEMORY_TABLE}
189
+ SET archivedAt = time::now(), validUntil = time::now()
190
+ WHERE id = $loserId AND archivedAt IS NONE`,
191
+ { loserId: ensureRecordId(loser.id, TABLES.MEMORY) },
192
+ ),
193
+ ),
194
+ )
195
+ yield* tryMemoryConsolidationEffect('relate-memories', () =>
196
+ db().relate(
197
+ ensureRecordId(winner.id, TABLES.MEMORY),
198
+ MEMORY_RELATION_TABLE,
199
+ ensureRecordId(loser.id, TABLES.MEMORY),
200
+ { relationType: RELATION_SUPERSEDES, confidence: 1.0 },
201
+ ),
202
+ )
203
+ yield* tryMemoryConsolidationEffect('write-history', () =>
204
+ db().insert<Record<string, unknown>>(MEMORY_HISTORY_TABLE, {
205
+ memoryId: ensureRecordId(loser.id, TABLES.MEMORY),
206
+ prevValue: loser.content,
207
+ event: 'DELETE',
208
+ }),
209
+ )
152
210
 
153
- archived.add(loser.id)
154
- mergeCount++
211
+ archived.add(loser.id)
212
+ mergeCount++
155
213
 
156
- if (!keepMemory) break
214
+ if (!keepMemory) continue
215
+ }
157
216
  }
158
- }
159
217
 
160
- return mergeCount
218
+ return mergeCount
219
+ })
161
220
  }
162
221
 
163
- async function pruneStaleMemories(): Promise<number> {
164
- const stale = await databaseService.query<{ id: RecordIdInput }>(
165
- new BoundQuery(
166
- `SELECT id FROM ${MEMORY_TABLE}
167
- WHERE accessCount = 0
168
- AND createdAt < time::now() - 90d
169
- AND archivedAt IS NONE
170
- AND importance < 0.5
171
- LIMIT 200`,
172
- ),
173
- )
222
+ function pruneStaleMemoriesEffect() {
223
+ return Effect.gen(function* () {
224
+ const stale = yield* tryMemoryConsolidationEffect('load-stale', () =>
225
+ db().query<{ id: RecordIdInput }>(
226
+ new BoundQuery(
227
+ `SELECT id FROM ${MEMORY_TABLE}
228
+ WHERE accessCount = 0
229
+ AND createdAt < time::now() - 90d
230
+ AND archivedAt IS NONE
231
+ AND importance < 0.5
232
+ LIMIT 200`,
233
+ ),
234
+ ),
235
+ )
174
236
 
175
- if (stale.length === 0) return 0
237
+ if (stale.length === 0) return 0
176
238
 
177
- const staleIds = stale.map((row) => ensureRecordId(row.id, TABLES.MEMORY))
178
- await databaseService.updateWhere(MEMORY_TABLE, inside('id', staleIds), { archivedAt: new Date() })
239
+ const staleIds = stale.map((row) => ensureRecordId(row.id, TABLES.MEMORY))
240
+ yield* tryMemoryConsolidationEffect('update-stale', () =>
241
+ db().updateWhere(MEMORY_TABLE, inside('id', staleIds), { archivedAt: nowDate() }),
242
+ )
179
243
 
180
- return stale.length
244
+ return stale.length
245
+ })
181
246
  }
182
247
 
183
- async function collapseSupersedeCh(): Promise<number> {
184
- const middleNodes = await databaseService.query<{
185
- middleId: RecordIdInput
186
- predecessors: RecordIdInput[]
187
- successors: RecordIdInput[]
188
- }>(
189
- new BoundQuery(
190
- `SELECT
191
- id AS middleId,
192
- <-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']<-${MEMORY_TABLE}.id AS predecessors,
193
- ->${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']->${MEMORY_TABLE}.id AS successors
194
- FROM ${MEMORY_TABLE}
195
- WHERE archivedAt IS NONE
196
- AND count(->${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
197
- AND count(<-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
198
- LIMIT ${MEMORY.MAX_KNN_LIMIT}`,
199
- ),
200
- )
201
-
202
- let collapsed = 0
203
-
204
- for (const node of middleNodes) {
205
- const predecessors = node.predecessors
206
- const successors = node.successors
207
-
208
- for (const predId of predecessors) {
209
- for (const succId of successors) {
210
- const predRef = ensureRecordId(predId, TABLES.MEMORY)
211
- const succRef = ensureRecordId(succId, TABLES.MEMORY)
212
- const existing = await databaseService.query<{ id: RecordIdInput }>(
213
- new BoundQuery(
214
- `SELECT id FROM ${MEMORY_RELATION_TABLE}
215
- WHERE in = $predId AND out = $succId AND relationType = '${RELATION_SUPERSEDES}'
216
- LIMIT 1`,
217
- { predId: predRef, succId: succRef },
218
- ),
219
- )
248
+ function collapseSupersededChainEffect() {
249
+ return Effect.gen(function* () {
250
+ const middleNodes = yield* tryMemoryConsolidationEffect('load-middle-nodes', () =>
251
+ db().query<{ middleId: RecordIdInput; predecessors: RecordIdInput[]; successors: RecordIdInput[] }>(
252
+ new BoundQuery(
253
+ `SELECT
254
+ id AS middleId,
255
+ <-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']<-${MEMORY_TABLE}.id AS predecessors,
256
+ ->${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']->${MEMORY_TABLE}.id AS successors
257
+ FROM ${MEMORY_TABLE}
258
+ WHERE archivedAt IS NONE
259
+ AND count(->${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
260
+ AND count(<-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
261
+ LIMIT ${MEMORY.MAX_KNN_LIMIT}`,
262
+ ),
263
+ ),
264
+ )
220
265
 
221
- if (existing.length === 0) {
222
- await databaseService.relate(predRef, MEMORY_RELATION_TABLE, succRef, {
223
- relationType: RELATION_SUPERSEDES,
224
- confidence: 1.0,
225
- })
266
+ let collapsed = 0
267
+
268
+ for (const node of middleNodes) {
269
+ for (const predId of node.predecessors) {
270
+ for (const succId of node.successors) {
271
+ const predRef = ensureRecordId(predId, TABLES.MEMORY)
272
+ const succRef = ensureRecordId(succId, TABLES.MEMORY)
273
+ const existing = yield* tryMemoryConsolidationEffect('read-existing-relation', () =>
274
+ db().query<{ id: RecordIdInput }>(
275
+ new BoundQuery(
276
+ `SELECT id FROM ${MEMORY_RELATION_TABLE}
277
+ WHERE in = $predId AND out = $succId AND relationType = '${RELATION_SUPERSEDES}'
278
+ LIMIT 1`,
279
+ { predId: predRef, succId: succRef },
280
+ ),
281
+ ),
282
+ )
283
+
284
+ if (existing.length === 0) {
285
+ yield* tryMemoryConsolidationEffect('write-relation', () =>
286
+ db().relate(predRef, MEMORY_RELATION_TABLE, succRef, {
287
+ relationType: RELATION_SUPERSEDES,
288
+ confidence: 1.0,
289
+ }),
290
+ )
291
+ }
226
292
  }
227
293
  }
228
- }
229
294
 
230
- await databaseService.updateWhere(MEMORY_TABLE, eq('id', ensureRecordId(node.middleId, TABLES.MEMORY)), {
231
- archivedAt: new Date(),
232
- })
295
+ yield* tryMemoryConsolidationEffect('update-middle-node', () =>
296
+ db().updateWhere(MEMORY_TABLE, eq('id', ensureRecordId(node.middleId, TABLES.MEMORY)), {
297
+ archivedAt: nowDate(),
298
+ }),
299
+ )
233
300
 
234
- collapsed++
235
- }
301
+ collapsed++
302
+ }
236
303
 
237
- return collapsed
304
+ return collapsed
305
+ })
238
306
  }
239
307
 
240
- async function decayImportance(): Promise<number> {
241
- const standardResult = await databaseService.query<{ count: number }>(
242
- new BoundQuery(
243
- `UPDATE ${MEMORY_TABLE}
244
- SET importance = math::max([0.1, importance * 0.95])
245
- WHERE lastAccessedAt IS NOT NONE
246
- AND lastAccessedAt < time::now() - 30d
247
- AND archivedAt IS NONE
248
- AND importance > 0.1
249
- AND (durability = 'standard' OR durability IS NONE)
250
- RETURN count() AS count`,
251
- ),
252
- )
253
-
254
- const ephemeralResult = await databaseService.query<{ count: number }>(
255
- new BoundQuery(
256
- `UPDATE ${MEMORY_TABLE}
257
- SET importance = math::max([0.1, importance * 0.85])
258
- WHERE lastAccessedAt IS NOT NONE
259
- AND lastAccessedAt < time::now() - 30d
260
- AND archivedAt IS NONE
261
- AND importance > 0.1
262
- AND durability = 'ephemeral'
263
- RETURN count() AS count`,
264
- ),
265
- )
308
+ function decayImportanceEffect() {
309
+ return Effect.gen(function* () {
310
+ const [standardResult, ephemeralResult] = yield* Effect.all([
311
+ tryMemoryConsolidationEffect('decay-standard', () =>
312
+ db().query<{ count: number }>(
313
+ new BoundQuery(
314
+ `UPDATE ${MEMORY_TABLE}
315
+ SET importance = math::max([0.1, importance * 0.95])
316
+ WHERE lastAccessedAt IS NOT NONE
317
+ AND lastAccessedAt < time::now() - 30d
318
+ AND archivedAt IS NONE
319
+ AND importance > 0.1
320
+ AND (durability = 'standard' OR durability IS NONE)
321
+ RETURN count() AS count`,
322
+ ),
323
+ ),
324
+ ),
325
+ tryMemoryConsolidationEffect('decay-ephemeral', () =>
326
+ db().query<{ count: number }>(
327
+ new BoundQuery(
328
+ `UPDATE ${MEMORY_TABLE}
329
+ SET importance = math::max([0.1, importance * 0.85])
330
+ WHERE lastAccessedAt IS NOT NONE
331
+ AND lastAccessedAt < time::now() - 30d
332
+ AND archivedAt IS NONE
333
+ AND importance > 0.1
334
+ AND durability = 'ephemeral'
335
+ RETURN count() AS count`,
336
+ ),
337
+ ),
338
+ ),
339
+ ])
266
340
 
267
- return (standardResult[0]?.count ?? 0) + (ephemeralResult[0]?.count ?? 0)
341
+ return (standardResult[0]?.count ?? 0) + (ephemeralResult[0]?.count ?? 0)
342
+ })
268
343
  }
269
344
 
270
- async function cleanupOrphanedRelations(): Promise<number> {
271
- const result = await databaseService.query<{ count: number }>(
272
- new BoundQuery(
273
- `DELETE ${MEMORY_RELATION_TABLE}
274
- WHERE in.archivedAt IS NOT NONE OR out.archivedAt IS NOT NONE
275
- RETURN count() AS count`,
276
- ),
277
- )
278
- return result[0]?.count ?? 0
345
+ function cleanupOrphanedRelationsEffect() {
346
+ return Effect.gen(function* () {
347
+ const result = yield* tryMemoryConsolidationEffect('cleanup-relations', () =>
348
+ db().query<{ count: number }>(
349
+ new BoundQuery(
350
+ `DELETE ${MEMORY_RELATION_TABLE}
351
+ WHERE in.archivedAt IS NOT NONE OR out.archivedAt IS NOT NONE
352
+ RETURN count() AS count`,
353
+ ),
354
+ ),
355
+ )
356
+ return result[0]?.count ?? 0
357
+ })
279
358
  }
280
359
 
281
- const handler = async (job: SandboxedJob<MemoryConsolidationJob>) => {
282
- const targetScope = job.data.scopeId
283
-
284
- try {
285
- let totalMerged = 0
286
- if (targetScope) {
287
- totalMerged = await deduplicateScope(targetScope)
288
- } else {
289
- const scopeIds = await databaseService.query<string>(
290
- new BoundQuery(`SELECT VALUE scopeId FROM ${MEMORY_TABLE} WHERE archivedAt IS NONE GROUP BY scopeId`),
291
- )
292
-
293
- const results = await Promise.all(scopeIds.map(deduplicateScope))
294
- totalMerged = results.reduce((a, b) => a + b, 0)
295
- }
296
-
297
- const pruned = await pruneStaleMemories()
298
- const collapsed = await collapseSupersedeCh()
299
- const decayed = await decayImportance()
300
- const orphaned = await cleanupOrphanedRelations()
360
+ const handler = (job: SandboxedJob<MemoryConsolidationJob>) =>
361
+ Effect.runPromise(
362
+ Effect.gen(function* () {
363
+ const targetScope = job.data.scopeId
364
+
365
+ let totalMerged = 0
366
+ if (targetScope) {
367
+ totalMerged = yield* deduplicateScopeEffect(targetScope)
368
+ } else {
369
+ const scopeIds = yield* tryMemoryConsolidationEffect('load-scope-ids', () =>
370
+ db().query<string>(
371
+ new BoundQuery(`SELECT VALUE scopeId FROM ${MEMORY_TABLE} WHERE archivedAt IS NONE GROUP BY scopeId`),
372
+ ),
373
+ )
301
374
 
302
- serverLogger.info`Memory consolidation complete (merged: ${totalMerged}, pruned: ${pruned}, collapsed: ${collapsed}, decayed: ${decayed}, orphaned relations: ${orphaned})`
303
- } catch (error) {
304
- const serialized = toSandboxedWorkerError(error, 'Memory consolidation job failed')
305
- serverLogger.error`${serialized.message}`
306
- throw serialized
307
- }
308
- }
375
+ const results = yield* Effect.forEach(scopeIds, deduplicateScopeEffect, { concurrency: 2 })
376
+ totalMerged = results.reduce((a, b) => a + b, 0)
377
+ }
378
+ const [pruned, collapsed, decayed, orphaned] = yield* Effect.all([
379
+ pruneStaleMemoriesEffect(),
380
+ collapseSupersededChainEffect(),
381
+ decayImportanceEffect(),
382
+ cleanupOrphanedRelationsEffect(),
383
+ ])
384
+
385
+ yield* Effect.sync(() => {
386
+ serverLogger.info`Memory consolidation complete (merged: ${totalMerged}, pruned: ${pruned}, collapsed: ${collapsed}, decayed: ${decayed}, orphaned relations: ${orphaned})`
387
+ })
388
+ }).pipe(
389
+ Effect.catch((error) => {
390
+ const serialized = toSandboxedWorkerError(error, 'Memory consolidation job failed')
391
+ return Effect.sync(() => {
392
+ serverLogger.error`${serialized.message}`
393
+ }).pipe(Effect.andThen(Effect.fail(serialized)))
394
+ }),
395
+ ),
396
+ )
309
397
 
310
398
  export default createTracedWorkerProcessor('memory-consolidation', handler)