@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
@@ -12,52 +12,51 @@ graph TB
12
12
  INDEX["Memory Indexer"]
13
13
  SEGMENT["Split into segments<br/>→ memory_segments"]
14
14
  EXTRACT_JOB["Enqueue extract_items job<br/>→ memory_jobs"]
15
- CONFLICT_RESOLVE_JOB["Enqueue resolve_pending_conflicts_for_message<br/>(dedupe by type+message+scope)<br/>→ memory_jobs"]
16
15
  SUMMARY_JOB["Enqueue build_conversation_summary<br/>→ memory_jobs"]
17
16
  end
18
17
 
19
18
  subgraph "Background Worker (polls every 1.5s)"
20
19
  WORKER["MemoryJobsWorker"]
21
- EMBED_SEG["embed_segment<br/>→ memory_embeddings"]
22
- EMBED_ITEM["embed_item<br/>→ memory_embeddings"]
23
- EMBED_SUM["embed_summary<br/>→ memory_embeddings"]
24
- EXTRACT["extract_items<br/>→ memory_items +<br/>memory_item_sources"]
25
- CHECK_CONTRA["check_contradictions<br/>→ contradiction/update merge OR<br/>pending_clarification + memory_item_conflicts"]
26
- RESOLVE_PENDING["resolve_pending_conflicts_for_message<br/>message-scoped clarification resolution<br/>→ resolved conflict + item status updates"]
27
- CLEAN_CONFLICTS["cleanup_resolved_conflicts<br/>delete resolved conflict rows<br/>older than retention window"]
28
- CLEAN_SUPERSEDED["cleanup_stale_superseded_items<br/>delete stale superseded items<br/>and item embedding rows"]
29
- EXTRACT_ENTITIES["extract_entities<br/>→ memory_entities +<br/>memory_item_entities +<br/>memory_entity_relations"]
30
- BACKFILL_REL["backfill_entity_relations<br/>checkpointed message scan<br/>→ enqueue extract_entities"]
20
+ EMBED_SEG["embed_segment<br/>→ Qdrant (dense + sparse)"]
21
+ EMBED_ITEM["embed_item<br/>→ Qdrant (dense + sparse)"]
22
+ EMBED_SUM["embed_summary<br/>→ Qdrant (dense + sparse)"]
23
+ EXTRACT["extract_items<br/>→ memory_items +<br/>memory_item_sources<br/>(LLM-directed supersession)"]
24
+ CLEAN_SUPERSEDED["cleanup_stale_superseded_items<br/>delete stale superseded items<br/>and Qdrant vectors"]
31
25
  BUILD_SUM["build_conversation_summary<br/>→ memory_summaries"]
32
- WEEKLY["refresh_weekly_summary<br/>→ memory_summaries"]
33
26
  end
34
27
 
35
- subgraph "Embedding Providers"
28
+ subgraph "Embedding Provider Selection (selectEmbeddingBackend)"
29
+ PROVIDER_SELECT["Provider Selection<br/>auto: local → OpenAI → Gemini → Ollama<br/>or explicit config override"]
36
30
  LOCAL_EMB["Local (ONNX)<br/>bge-small-en-v1.5"]
37
31
  OAI_EMB["OpenAI<br/>text-embedding-3-small"]
38
32
  GEM_EMB["Gemini<br/>gemini-embedding-001"]
39
33
  OLL_EMB["Ollama<br/>nomic-embed-text"]
40
34
  end
41
35
 
36
+ subgraph "Sparse Embedding (in-process)"
37
+ SPARSE_GEN["generateSparseEmbedding()<br/>TF-IDF, FNV-1a hashing<br/>(no external calls)"]
38
+ end
39
+
40
+ subgraph "Qdrant Vector Store"
41
+ DENSE["Named vector: dense<br/>(cosine similarity)"]
42
+ SPARSE["Named vector: sparse<br/>(TF-IDF based)"]
43
+ RRF["Query API:<br/>Reciprocal Rank Fusion"]
44
+ end
45
+
42
46
  subgraph "Read Path (Memory Recall)"
47
+ NEEDS_MEM["needsMemory gate<br/>(skip short/empty/tool-result turns)"]
43
48
  QUERY["Recall Query Builder<br/>User request + compacted context summary"]
44
- CONFLICT_GATE["Soft Conflict Gate<br/>dismiss non-actionable conflicts (kind + statement + provenance policy)<br/>attempt internal resolution from user turn<br/>relevance-based; never produces user-facing prompts"]
45
- PROFILE_BUILD["Dynamic Profile Compiler<br/>active trusted profile memories<br/>user_confirmed > user_reported > assistant_inferred"]
46
- PROFILE_INJECT["Inject profile context block<br/>into runtime user tail<br/>(strict token cap)"]
47
49
  BUDGET["Dynamic Recall Budget<br/>computeRecallBudget()<br/>from prompt headroom"]
48
- LEX["Lexical Search<br/>FTS5 on memory_segment_fts"]
49
- SEM["Semantic Search<br/>Qdrant cosine similarity"]
50
- ENTITY_SEARCH["Entity Search<br/>Seed name/alias matching"]
51
- REL_EXPAND["Relation Expansion<br/>1-hop via memory_entity_relations<br/>→ neighbor item links"]
52
- DIRECT["Direct Item Search<br/>LIKE on subject/statement"]
50
+ EMBED_Q["Generate dense + sparse<br/>query embeddings"]
51
+ HYBRID["Hybrid Search<br/>dense + sparse RRF on Qdrant"]
52
+ RECENCY["Recency Search<br/>conversation-scoped, DB only"]
53
+ MERGE["Merge + Deduplicate<br/>weighted score combination"]
53
54
  SCOPE["Scope Filter<br/>scope_id filtering<br/>(strict | global_fallback)<br/>Private threads: own scope + 'default'"]
54
- MERGE["RRF Merge<br/>+ Trust Weighting<br/>+ Freshness Decay"]
55
- CAPS["Source Caps<br/>bound per-source candidate count"]
56
- RERANK["LLM Re-ranking<br/>(Haiku, optional)"]
57
- TRIM["Token Trim<br/>maxInjectTokens override<br/>or static fallback"]
58
- INJECT["Attention-ordered<br/>Injection into prompt"]
59
- TELEMETRY["Emit memory_recalled<br/>hits + relation counters +<br/>ranking diagnostics"]
60
- STRIP_PROFILE["Strip injected dynamic profile block<br/>before persisting conversation history"]
55
+ TIER["Tier Classification<br/>score > 0.8 → tier 1<br/>score > 0.6 → tier 2<br/>below → dropped"]
56
+ STALE["Staleness Computation<br/>kind-specific lifetimes<br/>+ reinforcement from<br/>source conversation count"]
57
+ DEMOTE["Stale Demotion<br/>very_stale tier 1 → tier 2"]
58
+ INJECT["Two-Layer XML Injection<br/>budget-aware rendering"]
59
+ TELEMETRY["Emit memory_recalled<br/>tier counts + hybrid search ms +<br/>staleness stats"]
61
60
  end
62
61
 
63
62
  subgraph "Context Window Management"
@@ -83,49 +82,47 @@ graph TB
83
82
  STORE --> INDEX
84
83
  INDEX --> SEGMENT
85
84
  INDEX --> EXTRACT_JOB
86
- INDEX --> CONFLICT_RESOLVE_JOB
87
85
  INDEX --> SUMMARY_JOB
88
86
 
89
87
  WORKER --> EMBED_SEG
90
88
  WORKER --> EMBED_ITEM
91
89
  WORKER --> EMBED_SUM
92
90
  WORKER --> EXTRACT
93
- WORKER --> CHECK_CONTRA
94
- WORKER --> RESOLVE_PENDING
95
- WORKER --> CLEAN_CONFLICTS
96
91
  WORKER --> CLEAN_SUPERSEDED
97
- WORKER --> EXTRACT_ENTITIES
98
- WORKER --> BACKFILL_REL
99
92
  WORKER --> BUILD_SUM
100
- WORKER --> WEEKLY
101
- EXTRACT --> CHECK_CONTRA
102
- EXTRACT --> EXTRACT_ENTITIES
103
-
104
- EMBED_SEG --> OAI_EMB
105
- EMBED_SEG --> GEM_EMB
106
- EMBED_SEG --> OLL_EMB
107
-
108
- QUERY --> CONFLICT_GATE
109
- CONFLICT_GATE --> PROFILE_BUILD
110
- PROFILE_BUILD --> PROFILE_INJECT
111
- CONFLICT_GATE --> LEX
112
- CONFLICT_GATE --> SEM
113
- CONFLICT_GATE --> ENTITY_SEARCH
114
- CONFLICT_GATE --> DIRECT
115
- LEX --> SCOPE
116
- SEM --> SCOPE
117
- ENTITY_SEARCH --> REL_EXPAND
118
- REL_EXPAND --> SCOPE
119
- DIRECT --> SCOPE
93
+
94
+ EMBED_SEG --> PROVIDER_SELECT
95
+ EMBED_ITEM --> PROVIDER_SELECT
96
+ EMBED_SUM --> PROVIDER_SELECT
97
+ PROVIDER_SELECT --> LOCAL_EMB
98
+ PROVIDER_SELECT --> OAI_EMB
99
+ PROVIDER_SELECT --> GEM_EMB
100
+ PROVIDER_SELECT --> OLL_EMB
101
+ LOCAL_EMB --> DENSE
102
+ OAI_EMB --> DENSE
103
+ GEM_EMB --> DENSE
104
+ OLL_EMB --> DENSE
105
+ EMBED_SEG --> SPARSE_GEN
106
+ EMBED_ITEM --> SPARSE_GEN
107
+ EMBED_SUM --> SPARSE_GEN
108
+ SPARSE_GEN --> SPARSE
109
+
110
+ NEEDS_MEM --> QUERY
111
+ QUERY --> EMBED_Q
112
+ EMBED_Q --> PROVIDER_SELECT
113
+ EMBED_Q --> SPARSE_GEN
114
+ EMBED_Q --> HYBRID
115
+ HYBRID --> RRF
116
+ QUERY --> RECENCY
117
+ HYBRID --> SCOPE
118
+ RECENCY --> SCOPE
120
119
  SCOPE --> MERGE
121
- MERGE --> CAPS
122
- CAPS --> RERANK
123
- RERANK --> TRIM
124
- BUDGET --> TRIM
125
- TRIM --> INJECT
126
- PROFILE_INJECT --> INJECT
120
+ MERGE --> TIER
121
+ TIER --> STALE
122
+ STALE --> DEMOTE
123
+ BUDGET --> INJECT
124
+ DEMOTE --> INJECT
127
125
  INJECT --> TELEMETRY
128
- INJECT --> STRIP_PROFILE
129
126
 
130
127
  CTX --> COMPACT
131
128
  COMPACT --> GUARDS
@@ -158,92 +155,159 @@ The key distinction: normal compaction is a cost-optimized background process th
158
155
 
159
156
  ### Memory Retrieval Config Knobs (Defaults)
160
157
 
161
- | Config key | Default | Purpose |
162
- | --------------------------------------------------------- | ----------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------ |
163
- | `memory.retrieval.dynamicBudget.enabled` | `true` | Toggle per-turn recall budget calculation from live prompt headroom. |
164
- | `memory.retrieval.dynamicBudget.minInjectTokens` | `1200` | Lower clamp for computed recall injection budget. |
165
- | `memory.retrieval.dynamicBudget.maxInjectTokens` | `10000` | Upper clamp for computed recall injection budget. |
166
- | `memory.retrieval.dynamicBudget.targetHeadroomTokens` | `10000` | Reserved headroom to keep free for response generation/tool traces. |
167
- | `memory.entity.extractRelations.enabled` | `true` | Enable relation edge extraction and persistence in `memory_entity_relations`. |
168
- | `memory.entity.extractRelations.backfillBatchSize` | `200` | Batch size for checkpointed `backfill_entity_relations` jobs. |
169
- | `memory.entity.relationRetrieval.enabled` | `true` | Enable one-hop relation expansion from matched seed entities at recall time. |
170
- | `memory.entity.relationRetrieval.maxSeedEntities` | `8` | Maximum matched seed entities from the query. |
171
- | `memory.entity.relationRetrieval.maxNeighborEntities` | `20` | Maximum unique neighbor entities expanded from relation edges. |
172
- | `memory.entity.relationRetrieval.maxEdges` | `40` | Maximum relation edges traversed during expansion. |
173
- | `memory.entity.relationRetrieval.neighborScoreMultiplier` | `0.7` | Downweight multiplier for relation-expanded candidates vs direct entity hits. |
174
- | `memory.conflicts.enabled` | `true` | Enable soft conflict gate for unresolved `memory_item_conflicts`. |
175
- | `memory.conflicts.resolverLlmTimeoutMs` | `12000` | Timeout bound for clarification resolver LLM fallback. |
176
- | `memory.conflicts.relevanceThreshold` | `0.3` | Similarity threshold for deciding whether a pending conflict is relevant to the current request. |
177
- | `memory.conflicts.gateMode` | `'soft'` | Conflict gate strategy. Currently only `'soft'` is supported (resolves conflicts internally without user prompts). |
178
- | `memory.conflicts.conflictableKinds` | `['preference', 'profile', 'constraint', 'instruction', 'style']` | Memory item kinds eligible for conflict detection. Items with kinds outside this list are auto-dismissed. |
179
- | `memory.profile.enabled` | `true` | Enable dynamic profile compilation from active trusted profile/preference/constraint/instruction memories. |
180
- | `memory.profile.maxInjectTokens` | `800` | Hard token cap enforced by `ProfileCompiler` when generating the runtime profile block. |
158
+ | Config key | Default | Purpose |
159
+ | ----------------------------------------------------- | ------------------------: | -------------------------------------------------------------------- |
160
+ | `memory.retrieval.dynamicBudget.enabled` | `true` | Toggle per-turn recall budget calculation from live prompt headroom. |
161
+ | `memory.retrieval.dynamicBudget.minInjectTokens` | `1200` | Lower clamp for computed recall injection budget. |
162
+ | `memory.retrieval.dynamicBudget.maxInjectTokens` | `10000` | Upper clamp for computed recall injection budget. |
163
+ | `memory.retrieval.dynamicBudget.targetHeadroomTokens` | `10000` | Reserved headroom to keep free for response generation/tool traces. |
164
+ | `memory.retrieval.maxInjectTokens` | `10000` | Static fallback when dynamic budget is disabled. |
165
+ | `memory.retrieval.scopePolicy` | `'allow_global_fallback'` | Scope filtering strategy: `'strict'` or `'allow_global_fallback'`. |
181
166
 
182
167
  ### Memory Recall Debugging Playbook
183
168
 
184
169
  1. Run a recall-heavy turn and inspect `memory_recalled` events in the client trace stream.
185
170
  2. Validate baseline counters:
186
- - `lexicalHits`, `semanticHits`, `recencyHits`, `entityHits`
187
- - `relationSeedEntityCount`, `relationTraversedEdgeCount`, `relationNeighborEntityCount`, `relationExpandedItemCount`
171
+ - `semanticHits`, `recencyHits`
172
+ - `tier1Count`, `tier2Count`
173
+ - `hybridSearchLatencyMs`
188
174
  - `mergedCount`, `selectedCount`, `injectedTokens`, `latencyMs`
189
175
  3. Cross-check context pressure with `context_compacted` events:
190
176
  - `previousEstimatedInputTokens` vs `estimatedInputTokens`
191
177
  - `summaryCalls`, `compactedMessages`
192
178
  4. If dynamic budget is enabled, verify `injectedTokens` stays within the configured min/max clamps for `dynamicBudget`.
193
- 5. Run `bun run src/index.ts memory status` and confirm cleanup pressure signals:
194
- - `Pending conflicts`, `Resolved conflicts`, `Oldest pending conflict age`
195
- - job queue counts for `cleanup_resolved_conflicts` / `cleanup_stale_superseded_items`
196
- 6. Before tuning ranking or relation settings, run:
179
+ 5. Inspect staleness distribution in debug logs:
180
+ - `fresh`, `aging`, `stale`, `very_stale` counts
181
+ - Check for unexpected tier demotions (very_stale tier 1 items demoted to tier 2)
182
+ 6. Before tuning ranking settings, run:
197
183
  - `cd assistant && bun test src/__tests__/context-memory-e2e.test.ts`
198
184
  - `cd assistant && bun test src/__tests__/memory-context-benchmark.benchmark.test.ts`
199
185
  - `cd assistant && bun test src/__tests__/memory-recall-quality.test.ts`
200
- - `cd assistant && bun test src/__tests__/memory-regressions.test.ts -t "relation"`
201
186
  7. After tuning, rerun the same suite and compare:
202
- - relation counters (coverage)
187
+ - tier counts (coverage)
203
188
  - selected count / injected tokens (budget safety)
204
189
  - latency and ordering regressions via top candidate snapshots
205
190
 
206
- ### Conflict Lifecycle and Profile Hygiene
191
+ ### Write Path — Extraction and Supersession
207
192
 
208
193
  ```mermaid
209
194
  stateDiagram-v2
210
- [*] --> ActiveItems : extract_items/check_contradictions
211
- ActiveItems --> PendingConflict : ambiguous_contradiction\n(candidate -> pending_clarification)
212
- PendingConflict --> PendingConflict : internal evaluation\n(relevance check, no user prompt)
213
- PendingConflict --> Dismissed : non-actionable\n(kind policy + transient statement filter)
214
- PendingConflict --> ResolvedKeepExisting : clarification resolver\n+ applyConflictResolution
215
- PendingConflict --> ResolvedKeepCandidate : clarification resolver\n+ applyConflictResolution
216
- PendingConflict --> ResolvedMerge : clarification resolver\n+ applyConflictResolution
217
- ResolvedKeepExisting --> CleanupConflicts : cleanup_resolved_conflicts
218
- ResolvedKeepCandidate --> CleanupConflicts : cleanup_resolved_conflicts
219
- ResolvedMerge --> CleanupConflicts : cleanup_resolved_conflicts
220
- ResolvedKeepExisting --> SupersededItems : candidate superseded
221
- ResolvedMerge --> SupersededItems : merged-from candidate superseded
222
- SupersededItems --> CleanupItems : cleanup_stale_superseded_items
195
+ [*] --> ActiveItem : extract_items\n(LLM or pattern-based)
196
+ ActiveItem --> Superseded : explicit supersession\n(overrideConfidence = "explicit"\n+ supersedes = oldItemId)
197
+ ActiveItem --> ActiveItem : tentative/inferred override\n(both items coexist)
198
+ ActiveItem --> Superseded : subject-match fallback\n(same kind + subject,\nno LLM-directed supersession)
199
+ Superseded --> Cleanup : cleanup_stale_superseded_items\n(delete from DB + Qdrant)
223
200
  ```
224
201
 
225
- ### Internal-Only Conflict Handling
202
+ **Item extraction** uses LLM-powered extraction (with pattern-based fallback) to identify memorable information from conversation messages. Each extracted item belongs to one of six kinds:
203
+
204
+ | Kind | Description | Base Lifetime |
205
+ | ------------ | ------------------------------------------------- | ------------- |
206
+ | `identity` | Personal info, facts, relationships | 6 months |
207
+ | `preference` | Likes, dislikes, preferred approaches/tools | 3 months |
208
+ | `constraint` | Rules, requirements, directives | 1 month |
209
+ | `project` | Project details, repos, tech stacks, action items | 2 weeks |
210
+ | `decision` | Choices made, approaches selected | 2 weeks |
211
+ | `event` | Deadlines, milestones, meetings, dates | 3 days |
212
+
213
+ **Supersession chains** replace the old conflict resolution system. When the LLM extracts a new item that updates an existing one, it sets `supersedes` to the old item's ID and `overrideConfidence` to one of three levels:
214
+
215
+ - `explicit` — Clear override signal (e.g. "I changed my mind about X"). The old item is marked `superseded` and removed from Qdrant.
216
+ - `tentative` — Ambiguous; both items coexist as active.
217
+ - `inferred` — Weak signal; both items coexist (logged for observability).
218
+
219
+ A fallback subject-match supersession also runs for items without LLM-directed supersession: same kind + same subject = old item superseded.
220
+
221
+ **Semantic density gating** skips extraction for messages that are too short, consist of low-value filler (e.g. "ok", "thanks", "got it"), or have fewer than 3 words.
222
+
223
+ ### Read Path — Hybrid Recall Pipeline
224
+
225
+ The recall pipeline runs on every turn that passes the `needsMemory` gate (skips empty, very short, and tool-result-only turns). The pipeline is orchestrated by `buildMemoryRecall()` in `retriever.ts`:
226
+
227
+ 1. **Query construction** (`query-builder.ts`): Combines the user request text (up to 2000 chars) with any in-context session summary (up to 1200 chars).
228
+
229
+ 2. **Dense + sparse embedding generation**: The query is embedded using the configured embedding provider (auto-selection order: local → OpenAI → Gemini → Ollama). A TF-IDF sparse embedding is also generated in-process using FNV-1a hashing to a 30K vocabulary with sub-linear TF weighting and L2 normalization.
230
+
231
+ 3. **Hybrid search on Qdrant**: When both dense and sparse vectors are available, the pipeline uses Qdrant's query API with two prefetch stages (dense and sparse, each fetching up to 40 candidates) fused via Reciprocal Rank Fusion (RRF). Falls back to dense-only search when sparse vectors are unavailable.
232
+
233
+ 4. **Recency supplement**: A DB-only recency search fetches the 5 most recent segments from the current conversation, providing conversation-local context even when vector search misses.
234
+
235
+ 5. **Merge and deduplicate**: Hybrid and recency candidates are merged by key. Duplicate entries keep the highest scores from each source. A weighted final score is computed: `semantic * 0.7 + recency * 0.2 + confidence * 0.1`.
226
236
 
227
- Memory conflict resolution is entirely internal and non-interruptive. The conflict gate evaluates pending conflicts on each turn, dismisses non-actionable ones (based on kind policy, statement eligibility, coherence, and provenance), and attempts resolution when user input looks like a natural clarification. At no point does the conflict system produce user-facing clarification prompts, inject conflict instructions into the assistant's response, or block the user's request. The user is never aware that a conflict exists; the runtime response path always continues answering the user's actual request. This invariant is enforced across the conflict gate (`session-conflict-gate.ts`), session memory (`session-memory.ts`), session agent loop (`session-agent-loop.ts`), and runtime assembly (`session-runtime-assembly.ts`).
237
+ 6. **Tier classification** (`tier-classifier.ts`): Score-based, deterministic classification:
238
+ - `finalScore > 0.8` → **tier 1** (high relevance)
239
+ - `finalScore > 0.6` → **tier 2** (possibly relevant)
240
+ - Below 0.6 → dropped
228
241
 
229
- Runtime profile flow (per turn):
242
+ 7. **Staleness computation** (`staleness.ts`): Each item candidate is annotated with a staleness level based on its age relative to a kind-specific base lifetime (see table above). The effective lifetime is extended by a reinforcement factor: `baseLifetime * (1 + 0.3 * (sourceConversationCount - 1))`, so items mentioned across multiple conversations age more slowly. Staleness levels:
243
+ - `ratio < 0.5` → `fresh`
244
+ - `ratio <= 1.0` → `aging`
245
+ - `ratio <= 2.0` → `stale`
246
+ - `ratio > 2.0` → `very_stale`
230
247
 
231
- 1. `ProfileCompiler` builds a trusted profile block from active `profile` / `preference` / `constraint` / `instruction` items under strict token cap.
232
- 2. Session injects that block only into runtime prompt state.
233
- 3. Session strips the injected profile block before persisting conversation history, so dynamic profile context never pollutes durable message rows.
248
+ 8. **Stale demotion**: `very_stale` tier 1 candidates are demoted to tier 2, preventing old information from occupying prime injection space.
234
249
 
235
- ### Provenance-Aware Memory Pipeline
250
+ 9. **Two-layer XML injection** (`formatting.ts`): Budget-aware rendering into four XML sections:
236
251
 
237
- Every persisted message carries provenance metadata (`provenanceTrustClass`, `provenanceSourceChannel`, etc.) derived from the `TrustContext` resolved by `trust-context-resolver.ts`. This metadata records the trust class of the actor who produced the message and through which channel, enabling downstream trust decisions without re-resolving identity at read time.
252
+ ```xml
253
+ <memory_context>
254
+
255
+ <user_identity>
256
+ <!-- identity-kind tier 1 items (plain statements) -->
257
+ </user_identity>
258
+
259
+ <relevant_context>
260
+ <!-- tier 1 non-identity/non-preference items (episode-wrapped with source attribution) -->
261
+ </relevant_context>
262
+
263
+ <applicable_preferences>
264
+ <!-- preference/constraint tier 1 items (plain statements) -->
265
+ </applicable_preferences>
266
+
267
+ <possibly_relevant>
268
+ <!-- tier 2 items (episode-wrapped with staleness annotations) -->
269
+ </possibly_relevant>
270
+
271
+ </memory_context>
272
+ ```
273
+
274
+ Empty sections are omitted. Each section has a per-item token budget (150 tokens for tier 1, 100 for tier 2). Tier 1 sections consume budget first; tier 2 uses the remainder.
275
+
276
+ 10. **Injection strategy**: The rendered `<memory_context>` block is injected as a separate user + assistant acknowledgment message pair before the last user message (`injectMemoryRecallAsSeparateMessage`). This separates memory context from the user's actual query.
277
+
278
+ ### Internal-Only Trust Gating
279
+
280
+ **Provenance-aware pipeline**: Every persisted message carries provenance metadata (`provenanceTrustClass`, `provenanceSourceChannel`, etc.) derived from the `TrustContext` resolved by `trust-context-resolver.ts`.
238
281
 
239
282
  Two trust gates enforce trust-class-based access control over the memory pipeline:
240
283
 
241
- - **Write gate** (`indexer.ts`): The `extract_items` and `resolve_conflicts` jobs only run for messages from trusted actors (guardian or undefined provenance). Messages from untrusted actors (`trusted_contact`, `unknown`) are still segmented and embedded — so they appear in conversation context — but no profile extraction or conflict resolution is triggered. This prevents untrusted channels from injecting or mutating long-term memory items.
284
+ - **Write gate** (`indexer.ts`): The `extract_items` job only runs for messages from trusted actors (guardian or undefined provenance). Messages from untrusted actors (`trusted_contact`, `unknown`) are still segmented and embedded — so they appear in conversation context — but no item extraction is triggered. This prevents untrusted channels from injecting or mutating long-term memory items.
242
285
 
243
- - **Read gate** (`session-memory.ts`): When the current session's actor is untrusted, the memory recall pipeline returns a no-op context — no recall injection, no dynamic profile, no conflict resolution. This ensures untrusted actors cannot surface or exploit previously extracted memory.
286
+ - **Read gate** (`session-memory.ts`): When the current session's actor is untrusted, the memory recall pipeline returns a no-op context — no recall injection. This ensures untrusted actors cannot surface or exploit previously extracted memory.
244
287
 
245
288
  Trust policy is **cross-channel and trust-class-based**: decisions use `trustContext.trustClass`, not the channel string. Desktop sessions default to `trustClass: 'guardian'`. External channels (Telegram, WhatsApp, phone) provide explicit trust context via the resolver. Messages without provenance metadata are treated as trusted (guardian); all new messages carry provenance.
246
289
 
290
+ ### Embedding Backend Selection
291
+
292
+ The embedding backend is selected based on `memory.embeddings.provider` config:
293
+
294
+ - `auto` (default): Tries local → OpenAI → Gemini → Ollama, using the first available.
295
+ - `local`: ONNX-based local model (bge-small-en-v1.5). Lazy-loaded to avoid crashing in compiled binaries where onnxruntime-node is unavailable.
296
+ - `openai`: OpenAI text-embedding-3-small. Requires `apiKeys.openai`.
297
+ - `gemini`: Gemini gemini-embedding-001. Requires `apiKeys.gemini`. Only backend supporting multimodal embeddings (images, audio, video).
298
+ - `ollama`: Ollama nomic-embed-text. Requires Ollama to be configured.
299
+
300
+ An in-memory LRU vector cache (32 MB cap, keyed by `sha256(provider + model + content)`) avoids redundant embedding calls for identical content. Sparse embeddings are generated in-process (no external calls).
301
+
302
+ ### Graceful Degradation
303
+
304
+ When the embedding backend or Qdrant is unavailable:
305
+
306
+ - A **circuit breaker** on Qdrant (`qdrant-circuit-breaker.ts`) tracks consecutive failures and short-circuits search calls when the breaker is open.
307
+ - If embedding generation fails and `memory.embeddings.required` is `true`, recall returns an empty result with a degradation status (`embedding_generation_failed` or `embedding_provider_down`).
308
+ - If embeddings are optional (default), the pipeline falls back to recency-only search.
309
+ - Degradation status is reported to clients via `memory_status` events.
310
+
247
311
  ---
248
312
 
249
313
  ## Private Threads — Isolated Memory and Strict Side-Effect Controls
@@ -289,8 +353,6 @@ graph TB
289
353
 
290
354
  **Read fallback**: When recalling memories for a private thread, the retriever queries both the thread's own scope and the `'default'` scope. This ensures the assistant still has access to general knowledge (user profile, preferences, facts) learned in standard threads, while private-thread-specific memories take precedence in ranking. The fallback is implemented via `ScopePolicyOverride` with `fallbackToDefault: true`, which overrides the global scope policy on a per-call basis.
291
355
 
292
- **Profile compilation**: The `ProfileCompiler` also respects this dual-scope behavior for private threads — it includes profile/preference/constraint items from both the private scope and the default scope when building the runtime profile block.
293
-
294
356
  ### SessionMemoryPolicy
295
357
 
296
358
  The daemon derives a `SessionMemoryPolicy` from the conversation's `thread_type` and `memory_scope_id` when creating or restoring a session:
@@ -333,8 +395,7 @@ This ensures that file writes, bash commands, host operations, and other mutatin
333
395
  | `assistant/src/tools/executor.ts` | `forcePromptSideEffects` gate — promotes allow to prompt for side-effect tools |
334
396
  | `assistant/src/memory/search/types.ts` | `ScopePolicyOverride` interface for per-call scope control |
335
397
  | `assistant/src/memory/retriever.ts` | `buildScopeFilter()` — builds scope ID list from override or global config |
336
- | `assistant/src/memory/profile-compiler.ts` | Dual-scope profile compilation with `includeDefaultFallback` |
337
- | `assistant/src/daemon/session-memory.ts` | Wires `scopeId` and `includeDefaultFallback` into recall and profile compilation |
398
+ | `assistant/src/daemon/session-memory.ts` | Wires `scopeId` and `includeDefaultFallback` into recall |
338
399
 
339
400
  ---
340
401
 
@@ -387,7 +448,7 @@ graph TB
387
448
 
388
449
  ### Cache compatibility
389
450
 
390
- The Anthropic provider places `cache_control: { type: 'ephemeral' }` on the **last content block** of the last two user turns. Since workspace context is prepended (first block), the cache breakpoint correctly lands on the trailing user text or dynamic profile block. This is validated by dedicated cache-compatibility tests.
451
+ The Anthropic provider places `cache_control: { type: 'ephemeral' }` on the **last content block** of the last two user turns. Since workspace context is prepended (first block), the cache breakpoint correctly lands on the trailing user text block. This is validated by dedicated cache-compatibility tests.
391
452
 
392
453
  ### Key files
393
454
 
@@ -425,7 +486,7 @@ graph TB
425
486
 
426
487
  - **Fresh each turn**: `buildTemporalContext()` is called at the start of every agent loop invocation, ensuring the model always sees the current date even in long-running conversations.
427
488
  - **Clock source invariant**: Absolute time (`now`) always comes from the assistant host clock (`Date.now()`), never from channel/client clocks.
428
- - **Timezone precedence**: If `ui.userTimezone` is configured, temporal context uses it for local-date interpretation. Otherwise it falls back to dynamic profile memory, then assistant host timezone.
489
+ - **Timezone precedence**: If `ui.userTimezone` is configured, temporal context uses it for local-date interpretation. Otherwise it falls back to memory-stored timezone, then assistant host timezone.
429
490
  - **Timezone-aware**: Uses `Intl.DateTimeFormat` APIs for DST-safe date arithmetic and timezone validation/canonicalization.
430
491
  - **Bounded output**: Hard-capped at 1500 characters and 14 horizon entries to prevent prompt bloat.
431
492
  - **Runtime-only**: The injected `<temporal_context>` block is stripped from `this.messages` after the agent loop completes via `stripTemporalContext`. It never persists in conversation history.
package/knip.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "entry": ["src/**/*.test.ts", "src/**/__tests__/**/*.ts", "scripts/**/*.ts"],
3
+ "project": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*.ts"],
4
+ "ignore": [
5
+ "src/browser-extension-relay/client.ts",
6
+ "src/contacts/index.ts",
7
+ "src/daemon/main.ts",
8
+ "src/daemon/tls-certs.ts",
9
+ "src/errors.ts",
10
+ "src/events/index.ts",
11
+ "src/followups/index.ts",
12
+ "src/playbooks/index.ts",
13
+ "src/runtime/auth/index.ts",
14
+ "src/tasks/candidate-store.ts",
15
+ "src/tools/browser/api-map.ts",
16
+ "src/tools/browser/auto-navigate.ts",
17
+ "src/tools/browser/headless-browser.ts",
18
+ "src/tools/browser/recording-store.ts",
19
+ "src/tools/tasks/index.ts"
20
+ ],
21
+ "ignoreDependencies": [
22
+ "@hono/node-server",
23
+ "@vellumai/cli",
24
+ "esbuild",
25
+ "hono",
26
+ "ink",
27
+ "preact",
28
+ "quicktype-core",
29
+ "tree-sitter-bash",
30
+ "typescript-json-schema"
31
+ ]
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.4.49",
3
+ "version": "0.4.51",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -16,13 +16,14 @@
16
16
  "format": "prettier --write .",
17
17
  "format:check": "prettier --check .",
18
18
  "lint": "eslint",
19
+ "lint:unused": "knip --include files,dependencies,unlisted",
19
20
  "typecheck": "bunx tsc --noEmit",
20
21
  "test": "bash scripts/test.sh",
21
22
  "test:coverage": "COVERAGE=true bash scripts/test.sh",
22
23
  "test:stable": "EXCLUDE_EXPERIMENTAL=true bash scripts/test.sh",
23
24
  "test:bench": "find src/__tests__ -maxdepth 1 -type f -name '*.benchmark.test.ts' -print0 | xargs -0 -P 1 -I {} bun test {}",
24
25
  "test:filesystem-tools": "bash scripts/test-filesystem-tools.sh",
25
- "postinstall": "cd .. && git config core.hooksPath || git config core.hooksPath .githooks 2>/dev/null || true"
26
+ "postinstall": "cd .. && (git config core.hooksPath || git config core.hooksPath .githooks 2>/dev/null || true) && ([ -f meta/feature-flags/sync-bundled-copies.ts ] && bun run meta/feature-flags/sync-bundled-copies.ts 2>/dev/null || true)"
26
27
  },
27
28
  "dependencies": {
28
29
  "@anthropic-ai/claude-agent-sdk": "^0.2.42",
@@ -869,11 +869,13 @@ describe("AgentLoop", () => {
869
869
  await loop.run([userMessage], () => {}, undefined, undefined, onCheckpoint);
870
870
 
871
871
  expect(checkpoints).toHaveLength(1);
872
- expect(checkpoints[0]).toEqual({
872
+ expect(checkpoints[0]).toMatchObject({
873
873
  turnIndex: 0,
874
874
  toolCount: 1,
875
875
  hasToolUse: true,
876
876
  });
877
+ // history should contain the full conversation at checkpoint time
878
+ expect(checkpoints[0].history.length).toBeGreaterThanOrEqual(3);
877
879
  });
878
880
 
879
881
  // 17. Returning 'continue' lets the loop proceed normally