@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -1,508 +0,0 @@
1
- import { eq } from "drizzle-orm";
2
-
3
- import { getConfig } from "../config/loader.js";
4
- import {
5
- createTimeout,
6
- extractToolUse,
7
- getConfiguredProvider,
8
- userMessage,
9
- } from "../providers/provider-send-message.js";
10
- import { getLogger } from "../util/logger.js";
11
- import { truncate } from "../util/truncate.js";
12
- import { areStatementsCoherent } from "./conflict-intent.js";
13
- import {
14
- isConflictKindEligible,
15
- isStatementConflictEligible,
16
- } from "./conflict-policy.js";
17
- import { createOrUpdatePendingConflict } from "./conflict-store.js";
18
- import { getDb, getSqlite, rawAll } from "./db.js";
19
- import { enqueueMemoryJob } from "./jobs-store.js";
20
- import { memoryItems } from "./schema.js";
21
- import { clampUnitInterval } from "./validation.js";
22
-
23
- const log = getLogger("memory-contradiction-checker");
24
-
25
- const CONTRADICTION_LLM_TIMEOUT_MS = 15_000;
26
-
27
- type Relationship =
28
- | "contradiction"
29
- | "update"
30
- | "complement"
31
- | "ambiguous_contradiction";
32
-
33
- interface ClassifyResult {
34
- relationship: Relationship;
35
- explanation: string;
36
- }
37
-
38
- const CONTRADICTION_SYSTEM_PROMPT = `You are a memory consistency checker. Given two statements about the same subject, determine their relationship.
39
-
40
- Classify the relationship as one of:
41
- - "contradiction": The new statement directly contradicts the old statement. They cannot both be true at the same time. Example: "User prefers dark mode" vs "User prefers light mode".
42
- - "update": The new statement provides updated or more specific information that supersedes the old statement, but does not contradict it. Example: "User works at Acme" vs "User works at Acme as a senior engineer".
43
- - "complement": The statements are compatible and provide different, non-overlapping information. Both can coexist. Example: "User likes TypeScript" vs "User prefers functional programming".
44
- - "ambiguous_contradiction": The statements appear to conflict, but there is not enough confidence to invalidate either statement without user clarification.
45
-
46
- Be conservative: only classify as "contradiction" when the statements are genuinely incompatible. Prefer "complement" when in doubt.`;
47
-
48
- /**
49
- * Check a newly extracted memory item against existing items for contradictions.
50
- * Searches for existing active items with similar subject/statement, then uses
51
- * LLM to classify the relationship and handle accordingly.
52
- */
53
- export async function checkContradictions(newItemId: string): Promise<void> {
54
- const db = getDb();
55
- const newItem = db
56
- .select()
57
- .from(memoryItems)
58
- .where(eq(memoryItems.id, newItemId))
59
- .get();
60
-
61
- if (!newItem || newItem.status !== "active") {
62
- log.debug(
63
- { newItemId },
64
- "Skipping contradiction check — item not found or not active",
65
- );
66
- return;
67
- }
68
-
69
- // Find existing active items with similar kind + subject
70
- const candidates = findSimilarItems(newItem);
71
- if (candidates.length === 0) {
72
- log.debug(
73
- { newItemId, subject: newItem.subject },
74
- "No similar items found for contradiction check",
75
- );
76
- return;
77
- }
78
-
79
- const provider = getConfiguredProvider();
80
- if (!provider) {
81
- log.debug("Configured provider unavailable for contradiction checking");
82
- return;
83
- }
84
-
85
- const config = getConfig();
86
-
87
- if (!isConflictKindEligible(newItem.kind, config.memory.conflicts)) {
88
- log.debug(
89
- { newItemId, kind: newItem.kind },
90
- "Skipping contradiction check — kind not eligible for conflicts",
91
- );
92
- return;
93
- }
94
-
95
- // Skip if the new item's statement is transient/non-durable
96
- if (
97
- !isStatementConflictEligible(
98
- newItem.kind,
99
- newItem.statement,
100
- config.memory.conflicts,
101
- )
102
- ) {
103
- log.debug(
104
- { newItemId, kind: newItem.kind },
105
- "Skipping contradiction check — statement is transient or non-durable",
106
- );
107
- return;
108
- }
109
-
110
- for (const existing of candidates) {
111
- // Skip candidate if its statement is transient/non-durable
112
- if (
113
- !isStatementConflictEligible(
114
- existing.kind,
115
- existing.statement,
116
- config.memory.conflicts,
117
- )
118
- ) {
119
- log.debug(
120
- { existingId: existing.id },
121
- "Skipping candidate — statement is transient or non-durable",
122
- );
123
- continue;
124
- }
125
-
126
- // Skip pairs with zero topical overlap — they are not real contradictions
127
- if (!areStatementsCoherent(existing.statement, newItem.statement)) {
128
- log.debug(
129
- { existingId: existing.id, newId: newItem.id },
130
- "Skipping candidate — zero statement overlap (incoherent pair)",
131
- );
132
- continue;
133
- }
134
-
135
- try {
136
- const result = await classifyRelationship(existing, newItem);
137
- const mutated = handleRelationship(result, existing, newItem);
138
- // Only stop when the new item itself was actually invalidated (update case)
139
- // or gated (ambiguous_contradiction). For contradiction, the old item is
140
- // invalidated but the new item remains active and should continue to be
141
- // checked against remaining candidates. Skip the break when the transaction
142
- // detected stale data and performed no mutation.
143
- if (
144
- mutated &&
145
- (result.relationship === "update" ||
146
- result.relationship === "ambiguous_contradiction")
147
- )
148
- break;
149
- } catch (err) {
150
- const message = err instanceof Error ? err.message : String(err);
151
- log.warn(
152
- { err: message, newItemId, existingId: existing.id },
153
- "Contradiction classification failed for pair",
154
- );
155
- }
156
- }
157
- }
158
-
159
- interface MemoryItemRow {
160
- id: string;
161
- kind: string;
162
- subject: string;
163
- statement: string;
164
- status: string;
165
- confidence: number;
166
- importance: number | null;
167
- scopeId: string;
168
- lastSeenAt: number;
169
- }
170
-
171
- /**
172
- * Find existing active items that are similar to the given item.
173
- * Uses LIKE queries on subject and keyword overlap on statement.
174
- */
175
- function findSimilarItems(item: MemoryItemRow): MemoryItemRow[] {
176
- // Extract significant words from subject for LIKE matching
177
- const subjectWords = item.subject
178
- .toLowerCase()
179
- .split(/[^a-z0-9_.-]+/g)
180
- .filter((w) => w.length >= 3);
181
-
182
- // Extract significant words from statement for additional matching
183
- const statementWords = item.statement
184
- .toLowerCase()
185
- .split(/[^a-z0-9_.-]+/g)
186
- .filter((w) => w.length >= 3);
187
-
188
- if (subjectWords.length === 0 && statementWords.length === 0) return [];
189
-
190
- // Build LIKE clauses for subject similarity
191
- const likeClauses: string[] = [];
192
- for (const word of subjectWords) {
193
- const escaped = escapeSqlLike(word);
194
- likeClauses.push(`LOWER(subject) LIKE '%${escaped}%'`);
195
- }
196
-
197
- // Also match on statement keywords (top 5 longest words for specificity)
198
- const topStatementWords = statementWords
199
- .sort((a, b) => b.length - a.length)
200
- .slice(0, 5);
201
- for (const word of topStatementWords) {
202
- const escaped = escapeSqlLike(word);
203
- likeClauses.push(`LOWER(statement) LIKE '%${escaped}%'`);
204
- }
205
-
206
- if (likeClauses.length === 0) return [];
207
-
208
- const sqlQuery = `
209
- SELECT id, kind, subject, statement, status, confidence, importance, scope_id, last_seen_at
210
- FROM memory_items
211
- WHERE status = 'active'
212
- AND invalid_at IS NULL
213
- AND kind = ?
214
- AND id <> ?
215
- AND scope_id = ?
216
- AND (${likeClauses.join(" OR ")})
217
- ORDER BY last_seen_at DESC
218
- LIMIT 10
219
- `;
220
-
221
- try {
222
- interface SimilarItemRow {
223
- id: string;
224
- kind: string;
225
- subject: string;
226
- statement: string;
227
- status: string;
228
- confidence: number;
229
- importance: number | null;
230
- scope_id: string;
231
- last_seen_at: number;
232
- }
233
- const rows = rawAll<SimilarItemRow>(
234
- sqlQuery,
235
- item.kind,
236
- item.id,
237
- item.scopeId,
238
- );
239
-
240
- return rows.map((row) => ({
241
- id: row.id,
242
- kind: row.kind,
243
- subject: row.subject,
244
- statement: row.statement,
245
- status: row.status,
246
- confidence: row.confidence,
247
- importance: row.importance,
248
- scopeId: row.scope_id,
249
- lastSeenAt: row.last_seen_at,
250
- }));
251
- } catch (err) {
252
- log.warn({ err }, "Failed to search for similar memory items");
253
- return [];
254
- }
255
- }
256
-
257
- /**
258
- * Use LLM to classify the relationship between two memory items.
259
- */
260
- async function classifyRelationship(
261
- existingItem: MemoryItemRow,
262
- newItem: MemoryItemRow,
263
- ): Promise<ClassifyResult> {
264
- const provider = getConfiguredProvider()!;
265
-
266
- const userContent = [
267
- `Subject: ${newItem.subject}`,
268
- "",
269
- `Old statement: ${existingItem.statement}`,
270
- `New statement: ${newItem.statement}`,
271
- ].join("\n");
272
-
273
- const { signal, cleanup } = createTimeout(CONTRADICTION_LLM_TIMEOUT_MS);
274
- try {
275
- const response = await provider.sendMessage(
276
- [userMessage(userContent)],
277
- [
278
- {
279
- name: "classify_relationship",
280
- description:
281
- "Classify the relationship between two memory statements",
282
- input_schema: {
283
- type: "object" as const,
284
- properties: {
285
- relationship: {
286
- type: "string",
287
- enum: [
288
- "contradiction",
289
- "update",
290
- "complement",
291
- "ambiguous_contradiction",
292
- ],
293
- description:
294
- "The relationship between the old and new statements",
295
- },
296
- explanation: {
297
- type: "string",
298
- description:
299
- "Brief explanation of why this relationship was chosen",
300
- },
301
- },
302
- required: ["relationship", "explanation"],
303
- },
304
- },
305
- ],
306
- CONTRADICTION_SYSTEM_PROMPT,
307
- {
308
- config: {
309
- modelIntent: "latency-optimized",
310
- max_tokens: 256,
311
- tool_choice: { type: "tool" as const, name: "classify_relationship" },
312
- },
313
- signal,
314
- },
315
- );
316
- cleanup();
317
-
318
- const toolBlock = extractToolUse(response);
319
- if (!toolBlock) {
320
- throw new Error("No tool_use block in contradiction check response");
321
- }
322
-
323
- const input = toolBlock.input as {
324
- relationship?: string;
325
- explanation?: string;
326
- };
327
- const relationship = input.relationship as Relationship;
328
- if (
329
- ![
330
- "contradiction",
331
- "update",
332
- "complement",
333
- "ambiguous_contradiction",
334
- ].includes(relationship)
335
- ) {
336
- throw new Error(`Invalid relationship type: ${relationship}`);
337
- }
338
-
339
- return {
340
- relationship,
341
- explanation: truncate(String(input.explanation ?? ""), 500, ""),
342
- };
343
- } finally {
344
- cleanup();
345
- }
346
- }
347
-
348
- /**
349
- * Handle the classified relationship between an existing and new memory item.
350
- *
351
- * Wrapped in a SQLite transaction so that multi-row mutations (e.g. invalidating
352
- * the old item AND setting validFrom on the new one) are atomic. The transaction
353
- * also re-verifies both items are still active before mutating, preventing a
354
- * TOCTOU race when multiple workers process contradictions concurrently.
355
- */
356
- function handleRelationship(
357
- result: ClassifyResult,
358
- existingItem: MemoryItemRow,
359
- newItem: MemoryItemRow,
360
- ): boolean {
361
- if (result.relationship === "complement") {
362
- log.debug(
363
- {
364
- existingId: existingItem.id,
365
- newId: newItem.id,
366
- explanation: result.explanation,
367
- },
368
- "Complement detected — keeping both items",
369
- );
370
- return false;
371
- }
372
-
373
- return getSqlite()
374
- .transaction(() => {
375
- const db = getDb();
376
- const now = Date.now();
377
-
378
- // Re-check both items inside the transaction to guard against concurrent mutations
379
- const freshExisting = db
380
- .select()
381
- .from(memoryItems)
382
- .where(eq(memoryItems.id, existingItem.id))
383
- .get();
384
- const freshNew = db
385
- .select()
386
- .from(memoryItems)
387
- .where(eq(memoryItems.id, newItem.id))
388
- .get();
389
-
390
- if (
391
- !freshExisting ||
392
- freshExisting.status !== "active" ||
393
- freshExisting.invalidAt != null
394
- ) {
395
- log.debug(
396
- { existingId: existingItem.id },
397
- "Existing item no longer active — skipping",
398
- );
399
- return false;
400
- }
401
- if (
402
- !freshNew ||
403
- (freshNew.status !== "active" &&
404
- result.relationship !== "ambiguous_contradiction") ||
405
- freshNew.invalidAt != null
406
- ) {
407
- log.debug(
408
- { newId: newItem.id },
409
- "New item no longer active — skipping",
410
- );
411
- return false;
412
- }
413
-
414
- switch (result.relationship) {
415
- case "contradiction": {
416
- log.info(
417
- {
418
- existingId: existingItem.id,
419
- newId: newItem.id,
420
- explanation: result.explanation,
421
- },
422
- "Contradiction detected — invalidating old item",
423
- );
424
- db.update(memoryItems)
425
- .set({ invalidAt: now })
426
- .where(eq(memoryItems.id, existingItem.id))
427
- .run();
428
- db.update(memoryItems)
429
- .set({ validFrom: now })
430
- .where(eq(memoryItems.id, newItem.id))
431
- .run();
432
- return true;
433
- }
434
- case "update": {
435
- log.debug(
436
- {
437
- existingId: existingItem.id,
438
- newId: newItem.id,
439
- explanation: result.explanation,
440
- },
441
- "Update detected — merging into existing item",
442
- );
443
- db.update(memoryItems)
444
- .set({
445
- statement: newItem.statement,
446
- lastSeenAt: Math.max(
447
- freshExisting.lastSeenAt,
448
- freshNew!.lastSeenAt,
449
- ),
450
- confidence: clampUnitInterval(
451
- Math.max(freshExisting.confidence, freshNew!.confidence),
452
- ),
453
- })
454
- .where(eq(memoryItems.id, existingItem.id))
455
- .run();
456
- enqueueMemoryJob("embed_item", { itemId: existingItem.id });
457
- db.update(memoryItems)
458
- .set({ invalidAt: now })
459
- .where(eq(memoryItems.id, newItem.id))
460
- .run();
461
- return true;
462
- }
463
- case "ambiguous_contradiction": {
464
- log.info(
465
- {
466
- existingId: existingItem.id,
467
- newId: newItem.id,
468
- explanation: result.explanation,
469
- },
470
- "Ambiguous contradiction detected — gating candidate pending clarification",
471
- );
472
- db.update(memoryItems)
473
- .set({ status: "pending_clarification" })
474
- .where(eq(memoryItems.id, newItem.id))
475
- .run();
476
- createOrUpdatePendingConflict({
477
- scopeId: newItem.scopeId,
478
- existingItemId: existingItem.id,
479
- candidateItemId: newItem.id,
480
- relationship: "ambiguous_contradiction",
481
- clarificationQuestion: buildClarificationQuestion(
482
- existingItem.statement,
483
- newItem.statement,
484
- ),
485
- });
486
- return true;
487
- }
488
- default:
489
- return false;
490
- }
491
- })
492
- .immediate();
493
- }
494
-
495
- function escapeSqlLike(s: string): string {
496
- return s.replace(/'/g, "''").replace(/%/g, "").replace(/_/g, "");
497
- }
498
-
499
- function buildClarificationQuestion(
500
- existingStatement: string,
501
- candidateStatement: string,
502
- ): string {
503
- const normalize = (input: string): string =>
504
- truncate(input.replace(/\s+/g, " ").trim(), 180, "");
505
- return `Pending conflict: "${normalize(
506
- existingStatement,
507
- )}" vs "${normalize(candidateStatement)}"`;
508
- }