@vellumai/assistant 0.4.49 → 0.4.50

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 (239) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/memory.md +180 -119
  4. package/package.json +2 -2
  5. package/src/__tests__/agent-loop.test.ts +3 -1
  6. package/src/__tests__/anthropic-provider.test.ts +114 -23
  7. package/src/__tests__/approval-cascade.test.ts +1 -15
  8. package/src/__tests__/approval-routes-http.test.ts +2 -0
  9. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  10. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  11. package/src/__tests__/checker.test.ts +13 -0
  12. package/src/__tests__/config-schema.test.ts +1 -68
  13. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  14. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  15. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  16. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  17. package/src/__tests__/credential-vault-unit.test.ts +4 -0
  18. package/src/__tests__/credential-vault.test.ts +13 -1
  19. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  20. package/src/__tests__/date-context.test.ts +93 -77
  21. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  22. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  23. package/src/__tests__/history-repair.test.ts +245 -0
  24. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  25. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  26. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  27. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  28. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  29. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  30. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  31. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  32. package/src/__tests__/memory-regressions.test.ts +477 -2841
  33. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  34. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  35. package/src/__tests__/mime-builder.test.ts +28 -0
  36. package/src/__tests__/native-web-search.test.ts +1 -0
  37. package/src/__tests__/oauth-cli.test.ts +572 -5
  38. package/src/__tests__/oauth-store.test.ts +120 -6
  39. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  40. package/src/__tests__/registry.test.ts +0 -1
  41. package/src/__tests__/relay-server.test.ts +46 -1
  42. package/src/__tests__/schedule-tools.test.ts +32 -0
  43. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  44. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  45. package/src/__tests__/secure-keys.test.ts +7 -2
  46. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  47. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  48. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  49. package/src/__tests__/session-agent-loop.test.ts +19 -15
  50. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  51. package/src/__tests__/session-error.test.ts +124 -2
  52. package/src/__tests__/session-history-web-search.test.ts +918 -0
  53. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  54. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  55. package/src/__tests__/session-queue.test.ts +37 -27
  56. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  57. package/src/__tests__/session-slash-known.test.ts +1 -15
  58. package/src/__tests__/session-slash-queue.test.ts +1 -15
  59. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  60. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  61. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  62. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  63. package/src/__tests__/skills-install-extract.test.ts +93 -0
  64. package/src/__tests__/skillssh-registry.test.ts +451 -0
  65. package/src/__tests__/trust-store.test.ts +15 -0
  66. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  67. package/src/agent/ax-tree-compaction.test.ts +51 -0
  68. package/src/agent/loop.ts +39 -12
  69. package/src/approvals/AGENTS.md +1 -1
  70. package/src/approvals/guardian-request-resolvers.ts +14 -2
  71. package/src/bundler/compiler-tools.ts +66 -2
  72. package/src/calls/call-domain.ts +132 -0
  73. package/src/calls/call-store.ts +6 -0
  74. package/src/calls/relay-server.ts +43 -5
  75. package/src/calls/relay-setup-router.ts +17 -1
  76. package/src/calls/twilio-config.ts +1 -1
  77. package/src/calls/types.ts +3 -1
  78. package/src/cli/commands/doctor.ts +4 -3
  79. package/src/cli/commands/mcp.ts +46 -59
  80. package/src/cli/commands/memory.ts +16 -165
  81. package/src/cli/commands/oauth/apps.ts +31 -2
  82. package/src/cli/commands/oauth/connections.ts +431 -97
  83. package/src/cli/commands/oauth/providers.ts +15 -1
  84. package/src/cli/commands/sessions.ts +5 -2
  85. package/src/cli/commands/skills.ts +173 -1
  86. package/src/cli/http-client.ts +0 -20
  87. package/src/cli/main-screen.tsx +2 -2
  88. package/src/cli/program.ts +5 -6
  89. package/src/cli.ts +4 -10
  90. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  91. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  92. package/src/config/bundled-tool-registry.ts +2 -5
  93. package/src/config/schema.ts +1 -12
  94. package/src/config/schemas/memory-lifecycle.ts +0 -9
  95. package/src/config/schemas/memory-processing.ts +0 -180
  96. package/src/config/schemas/memory-retrieval.ts +32 -104
  97. package/src/config/schemas/memory.ts +0 -10
  98. package/src/config/types.ts +0 -4
  99. package/src/context/window-manager.ts +4 -1
  100. package/src/daemon/config-watcher.ts +61 -3
  101. package/src/daemon/daemon-control.ts +1 -1
  102. package/src/daemon/date-context.ts +114 -31
  103. package/src/daemon/handlers/sessions.ts +18 -13
  104. package/src/daemon/handlers/skills.ts +20 -1
  105. package/src/daemon/history-repair.ts +72 -8
  106. package/src/daemon/host-cu-proxy.ts +55 -26
  107. package/src/daemon/lifecycle.ts +31 -3
  108. package/src/daemon/mcp-reload-service.ts +2 -2
  109. package/src/daemon/message-types/computer-use.ts +1 -12
  110. package/src/daemon/message-types/memory.ts +4 -16
  111. package/src/daemon/message-types/messages.ts +1 -0
  112. package/src/daemon/message-types/sessions.ts +4 -0
  113. package/src/daemon/server.ts +12 -1
  114. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  115. package/src/daemon/session-agent-loop.ts +334 -48
  116. package/src/daemon/session-error.ts +89 -6
  117. package/src/daemon/session-history.ts +17 -7
  118. package/src/daemon/session-media-retry.ts +6 -2
  119. package/src/daemon/session-memory.ts +69 -149
  120. package/src/daemon/session-process.ts +10 -1
  121. package/src/daemon/session-runtime-assembly.ts +49 -19
  122. package/src/daemon/session-surfaces.ts +4 -1
  123. package/src/daemon/session-tool-setup.ts +7 -1
  124. package/src/daemon/session.ts +12 -2
  125. package/src/instrument.ts +61 -1
  126. package/src/memory/admin.ts +2 -191
  127. package/src/memory/canonical-guardian-store.ts +38 -2
  128. package/src/memory/conversation-crud.ts +0 -33
  129. package/src/memory/conversation-queries.ts +22 -3
  130. package/src/memory/db-init.ts +28 -0
  131. package/src/memory/embedding-backend.ts +84 -8
  132. package/src/memory/embedding-types.ts +9 -1
  133. package/src/memory/indexer.ts +7 -46
  134. package/src/memory/items-extractor.ts +274 -76
  135. package/src/memory/job-handlers/backfill.ts +2 -127
  136. package/src/memory/job-handlers/cleanup.ts +2 -16
  137. package/src/memory/job-handlers/extraction.ts +2 -138
  138. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  139. package/src/memory/job-handlers/summarization.ts +3 -148
  140. package/src/memory/job-utils.ts +21 -59
  141. package/src/memory/jobs-store.ts +1 -159
  142. package/src/memory/jobs-worker.ts +9 -52
  143. package/src/memory/migrations/104-core-indexes.ts +3 -3
  144. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  145. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  146. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  147. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  148. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  149. package/src/memory/migrations/154-drop-fts.ts +20 -0
  150. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  151. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  152. package/src/memory/migrations/index.ts +7 -0
  153. package/src/memory/qdrant-client.ts +148 -51
  154. package/src/memory/raw-query.ts +1 -1
  155. package/src/memory/retriever.test.ts +294 -273
  156. package/src/memory/retriever.ts +421 -645
  157. package/src/memory/schema/calls.ts +2 -0
  158. package/src/memory/schema/memory-core.ts +3 -48
  159. package/src/memory/schema/oauth.ts +2 -0
  160. package/src/memory/search/formatting.ts +263 -176
  161. package/src/memory/search/lexical.ts +1 -254
  162. package/src/memory/search/ranking.ts +0 -455
  163. package/src/memory/search/semantic.ts +100 -14
  164. package/src/memory/search/staleness.ts +47 -0
  165. package/src/memory/search/tier-classifier.ts +21 -0
  166. package/src/memory/search/types.ts +15 -77
  167. package/src/memory/task-memory-cleanup.ts +4 -6
  168. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  169. package/src/oauth/byo-connection.test.ts +8 -1
  170. package/src/oauth/oauth-store.ts +113 -27
  171. package/src/oauth/seed-providers.ts +6 -0
  172. package/src/oauth/token-persistence.ts +11 -3
  173. package/src/permissions/defaults.ts +1 -0
  174. package/src/permissions/trust-store.ts +23 -1
  175. package/src/playbooks/playbook-compiler.ts +1 -1
  176. package/src/prompts/system-prompt.ts +18 -2
  177. package/src/providers/anthropic/client.ts +56 -126
  178. package/src/providers/types.ts +7 -1
  179. package/src/runtime/AGENTS.md +9 -0
  180. package/src/runtime/auth/route-policy.ts +6 -3
  181. package/src/runtime/guardian-reply-router.ts +24 -22
  182. package/src/runtime/http-server.ts +2 -2
  183. package/src/runtime/invite-redemption-service.ts +19 -1
  184. package/src/runtime/invite-service.ts +25 -0
  185. package/src/runtime/pending-interactions.ts +2 -2
  186. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  187. package/src/runtime/routes/conversation-routes.ts +9 -1
  188. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  189. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  190. package/src/runtime/routes/memory-item-routes.ts +503 -0
  191. package/src/runtime/routes/session-management-routes.ts +3 -3
  192. package/src/runtime/routes/settings-routes.ts +2 -2
  193. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  194. package/src/runtime/routes/workspace-routes.ts +2 -1
  195. package/src/security/keychain-broker-client.ts +17 -4
  196. package/src/security/secure-keys.ts +25 -3
  197. package/src/security/token-manager.ts +36 -36
  198. package/src/skills/catalog-install.ts +74 -18
  199. package/src/skills/skillssh-registry.ts +503 -0
  200. package/src/tools/assets/search.ts +5 -1
  201. package/src/tools/computer-use/definitions.ts +0 -10
  202. package/src/tools/computer-use/registry.ts +1 -1
  203. package/src/tools/credentials/vault.ts +1 -3
  204. package/src/tools/memory/definitions.ts +4 -13
  205. package/src/tools/memory/handlers.test.ts +83 -103
  206. package/src/tools/memory/handlers.ts +50 -85
  207. package/src/tools/schedule/create.ts +8 -1
  208. package/src/tools/schedule/update.ts +8 -1
  209. package/src/tools/skills/load.ts +25 -2
  210. package/src/__tests__/clarification-resolver.test.ts +0 -193
  211. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  212. package/src/__tests__/conflict-policy.test.ts +0 -269
  213. package/src/__tests__/conflict-store.test.ts +0 -372
  214. package/src/__tests__/contradiction-checker.test.ts +0 -361
  215. package/src/__tests__/entity-extractor.test.ts +0 -211
  216. package/src/__tests__/entity-search.test.ts +0 -1117
  217. package/src/__tests__/profile-compiler.test.ts +0 -392
  218. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  219. package/src/__tests__/session-profile-injection.test.ts +0 -557
  220. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  221. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  222. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  223. package/src/daemon/session-conflict-gate.ts +0 -167
  224. package/src/daemon/session-dynamic-profile.ts +0 -77
  225. package/src/memory/clarification-resolver.ts +0 -417
  226. package/src/memory/conflict-intent.ts +0 -205
  227. package/src/memory/conflict-policy.ts +0 -127
  228. package/src/memory/conflict-store.ts +0 -410
  229. package/src/memory/contradiction-checker.ts +0 -508
  230. package/src/memory/entity-extractor.ts +0 -535
  231. package/src/memory/format-recall.ts +0 -47
  232. package/src/memory/fts-reconciler.ts +0 -165
  233. package/src/memory/job-handlers/conflict.ts +0 -200
  234. package/src/memory/profile-compiler.ts +0 -195
  235. package/src/memory/recall-cache.ts +0 -117
  236. package/src/memory/search/entity.ts +0 -535
  237. package/src/memory/search/query-expansion.test.ts +0 -70
  238. package/src/memory/search/query-expansion.ts +0 -118
  239. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -6,10 +6,24 @@ import { parseConversation, parseMessage } from "./conversation-crud.js";
6
6
  import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
7
7
  import { getDb, rawAll } from "./db.js";
8
8
  import { conversations, messages } from "./schema.js";
9
- import { buildFtsMatchQuery } from "./search/lexical.js";
10
9
 
11
10
  const log = getLogger("conversation-store");
12
11
 
12
+ /**
13
+ * Build an FTS5 MATCH query string from natural text by extracting tokens.
14
+ * Used for messages_fts full-text search over conversation content.
15
+ */
16
+ function buildFtsMatchQuery(text: string): string | null {
17
+ const tokens = text
18
+ .toLowerCase()
19
+ .split(/[^a-z0-9_]+/g)
20
+ .map((token) => token.trim())
21
+ .filter((token) => token.length >= 2);
22
+ if (tokens.length === 0) return null;
23
+ const unique = [...new Set(tokens)].slice(0, 24);
24
+ return unique.map((token) => `"${token.replace(/"/g, '""')}"`).join(" OR ");
25
+ }
26
+
13
27
  export function listConversations(
14
28
  limit?: number,
15
29
  includeBackground = false,
@@ -160,7 +174,9 @@ export function isLastUserMessageToolResult(conversationId: string): boolean {
160
174
  Array.isArray(parsed) &&
161
175
  parsed.length > 0 &&
162
176
  parsed.every(
163
- (block: Record<string, unknown>) => block.type === "tool_result",
177
+ (block: Record<string, unknown>) =>
178
+ block.type === "tool_result" ||
179
+ block.type === "web_search_tool_result",
164
180
  )
165
181
  ) {
166
182
  return true;
@@ -375,7 +391,10 @@ function buildExcerpt(rawContent: string, query: string): string {
375
391
  if (typeof block === "object" && block != null) {
376
392
  if (block.type === "text" && typeof block.text === "string") {
377
393
  parts.push(block.text);
378
- } else if (block.type === "tool_result") {
394
+ } else if (
395
+ block.type === "tool_result" ||
396
+ block.type === "web_search_tool_result"
397
+ ) {
379
398
  const inner = Array.isArray(block.content) ? block.content : [];
380
399
  for (const ib of inner) {
381
400
  if (ib?.type === "text" && typeof ib.text === "string")
@@ -37,6 +37,7 @@ import {
37
37
  migrateBackfillContactInteractionStats,
38
38
  migrateBackfillGuardianPrincipalId,
39
39
  migrateBackfillUsageCacheAccounting,
40
+ migrateCallSessionInviteMetadata,
40
41
  migrateCallSessionMode,
41
42
  migrateCanonicalGuardianDeliveriesDestinationIndex,
42
43
  migrateCanonicalGuardianRequesterChatId,
@@ -49,7 +50,10 @@ import {
49
50
  migrateConversationsThreadTypeIndex,
50
51
  migrateDropAccountsTable,
51
52
  migrateDropAssistantIdColumns,
53
+ migrateDropConflicts,
54
+ migrateDropEntityTables,
52
55
  migrateDropLegacyMemberGuardianTables,
56
+ migrateDropMemorySegmentFts,
53
57
  migrateDropRemindersTable,
54
58
  migrateDropUsageCompositeIndexes,
55
59
  migrateFkCascadeRebuilds,
@@ -63,9 +67,12 @@ import {
63
67
  migrateGuardianVerificationPurpose,
64
68
  migrateGuardianVerificationSessions,
65
69
  migrateInviteCodeHashColumn,
70
+ migrateMemoryItemSupersession,
66
71
  migrateMessagesFtsBackfill,
67
72
  migrateNormalizePhoneIdentities,
68
73
  migrateNotificationDeliveryThreadDecision,
74
+ migrateOAuthAppsClientSecretPath,
75
+ migrateOAuthProvidersPingUrl,
69
76
  migrateReminderRoutingIntent,
70
77
  migrateRemindersToSchedules,
71
78
  migrateRenameGuardianVerificationValues,
@@ -344,6 +351,27 @@ export function initializeDb(): void {
344
351
  // 53. OAuth provider/app/connection tables
345
352
  createOAuthTables(database);
346
353
 
354
+ // 54. Add explicit client_secret_credential_path to oauth_apps
355
+ migrateOAuthAppsClientSecretPath(database);
356
+
357
+ // 55. Add ping_url column to oauth_providers
358
+ migrateOAuthProvidersPingUrl(database);
359
+
360
+ // 56. Add supersession tracking columns and override confidence to memory_items
361
+ migrateMemoryItemSupersession(database);
362
+
363
+ // 56b. Drop unused entity tables (entity search replaced by hybrid search on item statements)
364
+ migrateDropEntityTables(database);
365
+
366
+ // 57. Drop memory_segment_fts virtual table and triggers (replaced by Qdrant hybrid search)
367
+ migrateDropMemorySegmentFts(database);
368
+
369
+ // 58. Drop memory_item_conflicts table (conflict resolution system removed)
370
+ migrateDropConflicts(database);
371
+
372
+ // 59. Add invite metadata columns to call_sessions for outbound invite call routing
373
+ migrateCallSessionInviteMetadata(database);
374
+
347
375
  validateMigrationState(database);
348
376
 
349
377
  if (process.env.BUN_TEST === "1") {
@@ -11,14 +11,11 @@ import {
11
11
  embeddingInputContentHash,
12
12
  type MultimodalEmbeddingInput,
13
13
  normalizeEmbeddingInput,
14
+ type SparseEmbedding,
14
15
  type TextEmbeddingInput,
15
16
  } from "./embedding-types.js";
16
17
 
17
- export type {
18
- EmbeddingInput,
19
- MultimodalEmbeddingInput,
20
- TextEmbeddingInput,
21
- };
18
+ export type { EmbeddingInput, MultimodalEmbeddingInput, TextEmbeddingInput };
22
19
  export { embeddingInputContentHash, normalizeEmbeddingInput };
23
20
 
24
21
  const log = getLogger("memory-embeddings");
@@ -412,7 +409,12 @@ export async function embedWithBackend(
412
409
 
413
410
  // ── In-memory cache check (primary provider only) ──────────────
414
411
  const cached: (number[] | null)[] = inputs.map((input) => {
415
- const v = getFromVectorCache(primaryProvider, primaryModel, input, vectorExtras);
412
+ const v = getFromVectorCache(
413
+ primaryProvider,
414
+ primaryModel,
415
+ input,
416
+ vectorExtras,
417
+ );
416
418
  if (v && v.length === expectedDim) return v;
417
419
  return null;
418
420
  });
@@ -443,7 +445,8 @@ export async function embedWithBackend(
443
445
 
444
446
  // Skip text-only backends for multimodal inputs
445
447
  const hasNonText = inputsToEmbed.some(
446
- (i) => typeof i !== "string" && normalizeEmbeddingInput(i).type !== "text",
448
+ (i) =>
449
+ typeof i !== "string" && normalizeEmbeddingInput(i).type !== "text",
447
450
  );
448
451
  if (backend.provider !== "gemini" && hasNonText) {
449
452
  continue;
@@ -502,7 +505,8 @@ export async function embedWithBackend(
502
505
  }
503
506
  if (!anyBackendAttempted) {
504
507
  const hasMultimodal = inputs.some(
505
- (i) => typeof i !== "string" && normalizeEmbeddingInput(i).type !== "text",
508
+ (i) =>
509
+ typeof i !== "string" && normalizeEmbeddingInput(i).type !== "text",
506
510
  );
507
511
  if (hasMultimodal) {
508
512
  throw new Error(
@@ -614,3 +618,75 @@ function isOllamaConfigured(config: AssistantConfig): boolean {
614
618
  Boolean(getOllamaBaseUrlEnv())
615
619
  );
616
620
  }
621
+
622
+ // ── TF-IDF sparse embedding ───────────────────────────────────────
623
+ // Simple tokenizer + TF-IDF sparse encoder. Produces a SparseEmbedding
624
+ // with term indices (hashed to a fixed vocabulary) and TF-IDF weights.
625
+ // Can be upgraded to a learned sparse encoder (e.g. SPLADE) later.
626
+
627
+ const SPARSE_VOCAB_SIZE = 30_000;
628
+
629
+ /**
630
+ * Bump this version whenever the sparse embedding algorithm changes
631
+ * (e.g. hash function fix, tokenizer change) to trigger re-indexing
632
+ * of existing sparse vectors via the sentinel mismatch mechanism.
633
+ */
634
+ export const SPARSE_EMBEDDING_VERSION = 2;
635
+
636
+ /** Tokenize text into lowercase alphanumeric tokens (Unicode-aware). */
637
+ function tokenize(text: string): string[] {
638
+ return text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
639
+ }
640
+
641
+ /** Hash a token to a stable index in [0, vocabSize). */
642
+ function tokenHash(token: string, vocabSize: number): number {
643
+ // FNV-1a 32-bit hash for speed
644
+ let hash = 0x811c9dc5;
645
+ for (let i = 0; i < token.length; i++) {
646
+ hash ^= token.charCodeAt(i);
647
+ hash = Math.imul(hash, 0x01000193) >>> 0;
648
+ }
649
+ return hash % vocabSize;
650
+ }
651
+
652
+ /**
653
+ * Generate a TF-IDF-based sparse embedding for the given text.
654
+ *
655
+ * Term frequency is computed from the input. IDF is approximated using
656
+ * sub-linear TF weighting (1 + log(tf)) since we don't have a corpus-level
657
+ * document frequency table. This still produces useful sparse vectors for
658
+ * lexical matching via Qdrant's sparse vector support.
659
+ */
660
+ export function generateSparseEmbedding(text: string): SparseEmbedding {
661
+ const tokens = tokenize(text);
662
+ if (tokens.length === 0) {
663
+ return { indices: [], values: [] };
664
+ }
665
+
666
+ // Count term frequencies per hash bucket
667
+ const tf = new Map<number, number>();
668
+ for (const token of tokens) {
669
+ const idx = tokenHash(token, SPARSE_VOCAB_SIZE);
670
+ tf.set(idx, (tf.get(idx) ?? 0) + 1);
671
+ }
672
+
673
+ // Convert to sub-linear TF weights: 1 + log(tf)
674
+ const indices: number[] = [];
675
+ const values: number[] = [];
676
+ for (const [idx, count] of tf) {
677
+ indices.push(idx);
678
+ values.push(1 + Math.log(count));
679
+ }
680
+
681
+ // L2-normalize the sparse vector so scores are comparable
682
+ let norm = 0;
683
+ for (const v of values) norm += v * v;
684
+ norm = Math.sqrt(norm);
685
+ if (norm > 0) {
686
+ for (let i = 0; i < values.length; i++) {
687
+ values[i] /= norm;
688
+ }
689
+ }
690
+
691
+ return { indices, values };
692
+ }
@@ -42,11 +42,19 @@ export type MultimodalEmbeddingInput =
42
42
  /** Accepts raw strings as shorthand for text inputs. */
43
43
  export type EmbeddingInput = string | MultimodalEmbeddingInput;
44
44
 
45
- export function normalizeEmbeddingInput(input: EmbeddingInput): MultimodalEmbeddingInput {
45
+ export function normalizeEmbeddingInput(
46
+ input: EmbeddingInput,
47
+ ): MultimodalEmbeddingInput {
46
48
  if (typeof input === "string") return { type: "text", text: input };
47
49
  return input;
48
50
  }
49
51
 
52
+ /** Sparse vector representation: parallel arrays of term indices and weights. */
53
+ export interface SparseEmbedding {
54
+ indices: number[];
55
+ values: number[];
56
+ }
57
+
50
58
  export function embeddingInputContentHash(input: EmbeddingInput): string {
51
59
  const normalized = normalizeEmbeddingInput(input);
52
60
  const hash = createHash("sha256");
@@ -5,24 +5,17 @@ import { getConfig } from "../config/loader.js";
5
5
  import type { MemoryConfig } from "../config/types.js";
6
6
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
7
7
  import { getLogger } from "../util/logger.js";
8
- import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
9
8
  import { getDb } from "./db.js";
10
9
  import { selectedBackendSupportsMultimodal } from "./embedding-backend.js";
11
- import {
12
- enqueueMemoryJob,
13
- enqueueResolvePendingConflictsForMessageJob,
14
- } from "./jobs-store.js";
10
+ import { enqueueMemoryJob } from "./jobs-store.js";
15
11
  import {
16
12
  extractMediaBlocks,
17
13
  extractTextFromStoredMessageContent,
18
14
  } from "./message-content.js";
19
- import { bumpMemoryVersion } from "./recall-cache.js";
20
15
  import { memorySegments } from "./schema.js";
21
16
  import { segmentText } from "./segmenter.js";
22
17
 
23
18
  const log = getLogger("memory-indexer");
24
- const SUMMARY_JOB_CHECKPOINT_KEY = "memory:summary_jobs:last_scheduled_at";
25
- const SUMMARY_SCHEDULE_INTERVAL_MS = 6 * 60 * 60 * 1000;
26
19
 
27
20
  export interface IndexMessageInput {
28
21
  messageId: string;
@@ -34,8 +27,8 @@ export interface IndexMessageInput {
34
27
  /**
35
28
  * Trust class of the actor who produced this message, captured at
36
29
  * persist time. When `'guardian'` or `undefined` (legacy), extraction
37
- * and conflict resolution jobs run. Otherwise, the message is segmented
38
- * and embedded but no profile mutations are triggered.
30
+ * jobs run. Otherwise, the message is segmented and embedded but no
31
+ * profile mutations are triggered.
39
32
  */
40
33
  provenanceTrustClass?: TrustClass;
41
34
  }
@@ -52,7 +45,7 @@ export function indexMessageNow(
52
45
  if (!config.enabled) return { indexedSegments: 0, enqueuedJobs: 0 };
53
46
 
54
47
  // Provenance-based trust gating: only guardian and legacy (undefined) actors
55
- // are trusted for extraction and conflict resolution.
48
+ // are trusted for extraction.
56
49
  const isTrustedActor =
57
50
  input.provenanceTrustClass === "guardian" ||
58
51
  input.provenanceTrustClass === undefined;
@@ -75,9 +68,6 @@ export function indexMessageNow(
75
68
  const shouldExtract =
76
69
  input.role === "user" ||
77
70
  (input.role === "assistant" && config.extraction.extractFromAssistant);
78
- const shouldResolveConflicts =
79
- input.role === "user" && config.conflicts.enabled;
80
-
81
71
  // Check if the resolved embedding backend supports multimodal input.
82
72
  // Only enqueue embed_attachment jobs when it does (currently Gemini only).
83
73
  const supportsMultimodal = selectedBackendSupportsMultimodal(getConfig());
@@ -152,13 +142,6 @@ export function indexMessageNow(
152
142
  tx,
153
143
  );
154
144
  }
155
- if (shouldResolveConflicts && isTrustedActor) {
156
- enqueueResolvePendingConflictsForMessageJob(
157
- input.messageId,
158
- input.scopeId ?? "default",
159
- tx,
160
- );
161
- }
162
145
  enqueueMemoryJob(
163
146
  "build_conversation_summary",
164
147
  { conversationId: input.conversationId },
@@ -173,27 +156,18 @@ export function indexMessageNow(
173
156
  );
174
157
  }
175
158
 
176
- // Invalidate recall cache when synchronous segment writes changed content,
177
- // so lexical/recency retrieval doesn't serve stale results during worker lag.
178
- if (segments.length - skippedEmbedJobs > 0) {
179
- bumpMemoryVersion();
180
- }
181
-
182
- if (!isTrustedActor && (shouldExtract || shouldResolveConflicts)) {
159
+ if (!isTrustedActor && shouldExtract) {
183
160
  log.info(
184
- `Skipping extraction/conflict jobs for untrusted actor (trustClass=${input.provenanceTrustClass})`,
161
+ `Skipping extraction jobs for untrusted actor (trustClass=${input.provenanceTrustClass})`,
185
162
  );
186
163
  }
187
164
 
188
- enqueueSummaryRollupJobsIfDue();
189
-
190
165
  const extractionGated = !isTrustedActor;
191
166
  const enqueuedJobs =
192
167
  segments.length -
193
168
  skippedEmbedJobs +
194
169
  mediaBlocks.length +
195
- (shouldExtract && !extractionGated ? 2 : 1) +
196
- (shouldResolveConflicts && !extractionGated ? 1 : 0);
170
+ (shouldExtract && !extractionGated ? 2 : 1);
197
171
  return {
198
172
  indexedSegments: segments.length,
199
173
  enqueuedJobs,
@@ -222,19 +196,6 @@ export function getRecentSegmentsForConversation(
222
196
  .all();
223
197
  }
224
198
 
225
- function enqueueSummaryRollupJobsIfDue(): void {
226
- const now = Date.now();
227
- const raw = getMemoryCheckpoint(SUMMARY_JOB_CHECKPOINT_KEY);
228
- const last = raw ? Number.parseInt(raw, 10) : 0;
229
- if (Number.isFinite(last) && now - last < SUMMARY_SCHEDULE_INTERVAL_MS)
230
- return;
231
-
232
- enqueueMemoryJob("refresh_weekly_summary", {});
233
- enqueueMemoryJob("refresh_monthly_summary", {});
234
- setMemoryCheckpoint(SUMMARY_JOB_CHECKPOINT_KEY, String(now));
235
- log.debug("Scheduled periodic global summary jobs");
236
- }
237
-
238
199
  function buildSegmentId(messageId: string, segmentIndex: number): string {
239
200
  return `${messageId}:${segmentIndex}`;
240
201
  }