@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -98,52 +98,93 @@ export async function embedConceptPageJob(
98
98
  );
99
99
  }
100
100
 
101
- const contentHash = embeddingInputContentHash({ type: "text", text });
102
101
  const expectedDim = config.memory.qdrant.vectorSize;
103
- let provider = status.provider;
104
- let model = status.model!;
102
+ // The status provider is the cache lookup key for any prior row; the
103
+ // *actual* provider/model come back on the embedded result. They usually
104
+ // match, but a backend swap mid-run would surface here — body and summary
105
+ // are then re-embedded together so both rows write under the same identity.
106
+ const cacheProvider = status.provider;
107
+ const cacheModel = status.model!;
108
+
109
+ const db = getDb();
105
110
 
106
111
  // Cache lookup: same (targetType, targetId, provider, model) row gets
107
112
  // reused across runs as long as `contentHash` matches. The dim mismatch
108
113
  // check guards against a config change (vectorSize bumped) since the last
109
- // write — in that case we treat the row as stale and re-embed.
110
- const db = getDb();
111
- let cachedRow = db
112
- .select({
113
- vectorBlob: memoryEmbeddings.vectorBlob,
114
- vectorJson: memoryEmbeddings.vectorJson,
115
- dimensions: memoryEmbeddings.dimensions,
116
- contentHash: memoryEmbeddings.contentHash,
117
- })
118
- .from(memoryEmbeddings)
119
- .where(
120
- and(
121
- eq(memoryEmbeddings.targetType, CONCEPT_PAGE_TARGET_TYPE),
122
- eq(memoryEmbeddings.targetId, slug),
123
- eq(memoryEmbeddings.provider, provider),
124
- eq(memoryEmbeddings.model, model),
125
- ),
126
- )
127
- .get();
128
- if (cachedRow && cachedRow.dimensions !== expectedDim) cachedRow = undefined;
129
- if (cachedRow && cachedRow.contentHash !== contentHash) cachedRow = undefined;
130
-
131
- let dense: number[];
132
- let cacheHit = false;
133
- if (cachedRow) {
134
- dense = cachedRow.vectorBlob
135
- ? blobToVector(cachedRow.vectorBlob as Buffer)
136
- : (JSON.parse(cachedRow.vectorJson!) as number[]);
137
- cacheHit = true;
138
- } else {
139
- const embedded = await embedWithBackend(config, [{ type: "text", text }]);
140
- const vector = embedded.vectors[0];
141
- if (!vector) return;
142
- dense = vector;
143
- provider = embedded.provider;
144
- model = embedded.model;
114
+ // write — in that case we treat the row as stale and re-embed. The body
115
+ // and (optional) summary share the same provider/model — but each gets
116
+ // its own cache row keyed by a distinct targetId so summary edits don't
117
+ // invalidate the body cache and vice versa.
118
+ const bodyContentHash = embeddingInputContentHash({ type: "text", text });
119
+ const bodyCache = readEmbeddingCache(
120
+ db,
121
+ slug,
122
+ cacheProvider,
123
+ cacheModel,
124
+ expectedDim,
125
+ );
126
+ const bodyCacheHit = bodyCache?.contentHash === bodyContentHash;
127
+
128
+ // Optional summary embedding — only when the page has a `summary` in its
129
+ // frontmatter. Pages without one fall back to body-only retrieval at
130
+ // query time (the activation pipeline reads the summary score as
131
+ // undefined and uses the body score directly).
132
+ const summaryText = page.frontmatter.summary?.trim() ?? "";
133
+ const hasSummary = summaryText.length > 0;
134
+ const summaryCacheId = `${slug}#summary`;
135
+ const summaryContentHash = hasSummary
136
+ ? embeddingInputContentHash({ type: "text", text: summaryText })
137
+ : undefined;
138
+ const summaryCache = hasSummary
139
+ ? readEmbeddingCache(
140
+ db,
141
+ summaryCacheId,
142
+ cacheProvider,
143
+ cacheModel,
144
+ expectedDim,
145
+ )
146
+ : null;
147
+ const summaryCacheHit =
148
+ hasSummary && summaryCache?.contentHash === summaryContentHash;
149
+
150
+ // Batch all cache misses into one `embedWithBackend` call. Each backend
151
+ // round-trip is the dominant cost — fresh body + fresh summary in a
152
+ // single batch saves a round-trip vs serial calls and gives both vectors
153
+ // the same provider/model regardless of any backend rotation mid-run.
154
+ type Slot = "body" | "summary";
155
+ const toEmbed: Array<{ type: "text"; text: string }> = [];
156
+ const slots: Slot[] = [];
157
+ if (!bodyCacheHit) {
158
+ toEmbed.push({ type: "text", text });
159
+ slots.push("body");
160
+ }
161
+ if (hasSummary && !summaryCacheHit) {
162
+ toEmbed.push({ type: "text", text: summaryText });
163
+ slots.push("summary");
145
164
  }
146
165
 
166
+ let bodyDense: number[] | undefined = bodyCacheHit ? bodyCache!.dense : undefined;
167
+ let summaryDense: number[] | undefined = summaryCacheHit
168
+ ? summaryCache!.dense
169
+ : undefined;
170
+ let writeProvider = cacheProvider;
171
+ let writeModel = cacheModel;
172
+ if (toEmbed.length > 0) {
173
+ const embedded = await embedWithBackend(config, toEmbed);
174
+ writeProvider = embedded.provider;
175
+ writeModel = embedded.model;
176
+ for (let i = 0; i < slots.length; i++) {
177
+ const vector = embedded.vectors[i];
178
+ if (!vector) continue;
179
+ if (slots[i] === "body") bodyDense = vector;
180
+ else summaryDense = vector;
181
+ }
182
+ }
183
+ // Body embedding is the ground truth — without it the page can't surface.
184
+ // (Cache hit paths populate `bodyDense` above; a fresh embed that returned
185
+ // no vectors short-circuits here too.)
186
+ if (!bodyDense) return;
187
+
147
188
  // Sparse is cheap (in-process tokenization) and changes any time the body
148
189
  // changes, so we always recompute it rather than caching alongside dense.
149
190
  // BM25 weights live on the doc side; queries embed binary occurrence in
@@ -151,57 +192,42 @@ export async function embedConceptPageJob(
151
192
  // corpus for the first time), fall back to the legacy TF-only encoding —
152
193
  // the next reembed pass overwrites the page once stats are available.
153
194
  const corpusStats = getConceptPageCorpusStats();
154
- const sparse = corpusStats
155
- ? generateBm25DocEmbedding(text, corpusStats, {
156
- k1: config.memory.v2.bm25_k1,
157
- b: config.memory.v2.bm25_b,
158
- })
159
- : generateSparseEmbedding(text);
195
+ const encodeSparse = (input: string) =>
196
+ corpusStats
197
+ ? generateBm25DocEmbedding(input, corpusStats, {
198
+ k1: config.memory.v2.bm25_k1,
199
+ b: config.memory.v2.bm25_b,
200
+ })
201
+ : generateSparseEmbedding(input);
202
+ const sparse = encodeSparse(text);
203
+ const summarySparse = hasSummary ? encodeSparse(summaryText) : undefined;
160
204
 
161
205
  const now = Date.now();
162
206
  // Persist freshly embedded vectors for cross-restart reuse. On cache hit
163
207
  // the existing row already has identical content + hash, so the write
164
208
  // would be a no-op — skip it. Best-effort: write failure is not fatal,
165
209
  // we still want the Qdrant upsert below to fire.
166
- if (!cacheHit) {
167
- try {
168
- const blobValue = vectorToBlob(dense);
169
- db.insert(memoryEmbeddings)
170
- .values({
171
- id: randomUUID(),
172
- targetType: CONCEPT_PAGE_TARGET_TYPE,
173
- targetId: slug,
174
- provider,
175
- model,
176
- dimensions: dense.length,
177
- vectorBlob: blobValue,
178
- vectorJson: null,
179
- contentHash,
180
- createdAt: now,
181
- updatedAt: now,
182
- })
183
- .onConflictDoUpdate({
184
- target: [
185
- memoryEmbeddings.targetType,
186
- memoryEmbeddings.targetId,
187
- memoryEmbeddings.provider,
188
- memoryEmbeddings.model,
189
- ],
190
- set: {
191
- vectorBlob: blobValue,
192
- vectorJson: null,
193
- dimensions: dense.length,
194
- contentHash,
195
- updatedAt: now,
196
- },
197
- })
198
- .run();
199
- } catch (err) {
200
- log.warn(
201
- { err, slug },
202
- "Failed to write concept-page embedding cache row",
203
- );
204
- }
210
+ if (!bodyCacheHit) {
211
+ writeEmbeddingCache(db, {
212
+ slug,
213
+ cacheId: slug,
214
+ dense: bodyDense,
215
+ contentHash: bodyContentHash,
216
+ provider: writeProvider,
217
+ model: writeModel,
218
+ now,
219
+ });
220
+ }
221
+ if (hasSummary && !summaryCacheHit && summaryDense && summaryContentHash) {
222
+ writeEmbeddingCache(db, {
223
+ slug,
224
+ cacheId: summaryCacheId,
225
+ dense: summaryDense,
226
+ contentHash: summaryContentHash,
227
+ provider: writeProvider,
228
+ model: writeModel,
229
+ now,
230
+ });
205
231
  }
206
232
 
207
233
  // Apply anisotropy correction at the boundary between the (raw) cached
@@ -210,19 +236,129 @@ export async function embedConceptPageJob(
210
236
  // the cache survives and the (cheap) correction math reruns over each
211
237
  // cached vector. Pass-through when no calibration is fit yet.
212
238
  const correctedDense = await applyCorrectionIfCalibrated(
213
- dense,
214
- provider,
215
- model,
239
+ bodyDense,
240
+ writeProvider,
241
+ writeModel,
216
242
  );
243
+ const correctedSummaryDense = summaryDense
244
+ ? await applyCorrectionIfCalibrated(summaryDense, writeProvider, writeModel)
245
+ : undefined;
217
246
 
218
247
  await upsertConceptPageEmbedding({
219
248
  slug,
220
249
  dense: correctedDense,
221
250
  sparse,
251
+ summary:
252
+ correctedSummaryDense && summarySparse
253
+ ? { dense: correctedSummaryDense, sparse: summarySparse }
254
+ : undefined,
222
255
  updatedAt: now,
223
256
  });
224
257
  }
225
258
 
259
+ /** SQLite cache row shape returned by `readEmbeddingCache`. */
260
+ interface EmbeddingCacheEntry {
261
+ dense: number[];
262
+ contentHash: string;
263
+ }
264
+
265
+ /**
266
+ * Look up a cached dense vector keyed on `(targetType, targetId, provider,
267
+ * model)`. Returns the row only when the persisted dimensions match the
268
+ * configured expectation — a stale row from a previous `vectorSize` is
269
+ * treated as a cache miss so the caller re-embeds.
270
+ */
271
+ function readEmbeddingCache(
272
+ db: ReturnType<typeof getDb>,
273
+ cacheId: string,
274
+ provider: string,
275
+ model: string,
276
+ expectedDim: number,
277
+ ): EmbeddingCacheEntry | null {
278
+ const row = db
279
+ .select({
280
+ vectorBlob: memoryEmbeddings.vectorBlob,
281
+ vectorJson: memoryEmbeddings.vectorJson,
282
+ dimensions: memoryEmbeddings.dimensions,
283
+ contentHash: memoryEmbeddings.contentHash,
284
+ })
285
+ .from(memoryEmbeddings)
286
+ .where(
287
+ and(
288
+ eq(memoryEmbeddings.targetType, CONCEPT_PAGE_TARGET_TYPE),
289
+ eq(memoryEmbeddings.targetId, cacheId),
290
+ eq(memoryEmbeddings.provider, provider),
291
+ eq(memoryEmbeddings.model, model),
292
+ ),
293
+ )
294
+ .get();
295
+ if (!row || row.dimensions !== expectedDim) return null;
296
+ // A row without a contentHash is a legacy/corrupt entry — treat as a miss
297
+ // and force a re-embed rather than misalign the cache key.
298
+ if (row.contentHash === null) return null;
299
+ const dense = row.vectorBlob
300
+ ? blobToVector(row.vectorBlob as Buffer)
301
+ : (JSON.parse(row.vectorJson!) as number[]);
302
+ return { dense, contentHash: row.contentHash };
303
+ }
304
+
305
+ /**
306
+ * Persist a freshly embedded dense vector in the SQLite cache. Best-effort:
307
+ * a write failure is logged and swallowed so the Qdrant upsert still runs.
308
+ */
309
+ function writeEmbeddingCache(
310
+ db: ReturnType<typeof getDb>,
311
+ params: {
312
+ slug: string;
313
+ cacheId: string;
314
+ dense: number[];
315
+ contentHash: string;
316
+ provider: string;
317
+ model: string;
318
+ now: number;
319
+ },
320
+ ): void {
321
+ const { slug, cacheId, dense, contentHash, provider, model, now } = params;
322
+ try {
323
+ const blobValue = vectorToBlob(dense);
324
+ db.insert(memoryEmbeddings)
325
+ .values({
326
+ id: randomUUID(),
327
+ targetType: CONCEPT_PAGE_TARGET_TYPE,
328
+ targetId: cacheId,
329
+ provider,
330
+ model,
331
+ dimensions: dense.length,
332
+ vectorBlob: blobValue,
333
+ vectorJson: null,
334
+ contentHash,
335
+ createdAt: now,
336
+ updatedAt: now,
337
+ })
338
+ .onConflictDoUpdate({
339
+ target: [
340
+ memoryEmbeddings.targetType,
341
+ memoryEmbeddings.targetId,
342
+ memoryEmbeddings.provider,
343
+ memoryEmbeddings.model,
344
+ ],
345
+ set: {
346
+ vectorBlob: blobValue,
347
+ vectorJson: null,
348
+ dimensions: dense.length,
349
+ contentHash,
350
+ updatedAt: now,
351
+ },
352
+ })
353
+ .run();
354
+ } catch (err) {
355
+ log.warn(
356
+ { err, slug, cacheId },
357
+ "Failed to write concept-page embedding cache row",
358
+ );
359
+ }
360
+ }
361
+
226
362
  /**
227
363
  * Enqueue an `embed_concept_page` job (async, fire-and-forget). Modeled on
228
364
  * `enqueuePkbIndexJob` — callers that want a slug re-embedded after a write
@@ -18,6 +18,7 @@ export type MemoryJobType =
18
18
  | "embed_summary"
19
19
  | "prune_old_conversations"
20
20
  | "prune_old_llm_request_logs"
21
+ | "prune_old_trace_events"
21
22
  | "build_conversation_summary"
22
23
  | "conversation_analyze"
23
24
  | "backfill"
@@ -366,6 +367,53 @@ export function enqueuePruneOldConversationsJob(
366
367
  return enqueueMemoryJob("prune_old_conversations", payload);
367
368
  }
368
369
 
370
+ export function enqueuePruneOldTraceEventsJob(retentionDays?: number): string {
371
+ const db = getDb();
372
+ const existing = db
373
+ .select()
374
+ .from(memoryJobs)
375
+ .where(
376
+ and(
377
+ eq(memoryJobs.type, "prune_old_trace_events"),
378
+ inArray(memoryJobs.status, ["pending", "running"]),
379
+ ),
380
+ )
381
+ .orderBy(asc(memoryJobs.createdAt))
382
+ .get();
383
+ if (existing) {
384
+ if (
385
+ existing.status === "pending" &&
386
+ typeof retentionDays === "number" &&
387
+ Number.isFinite(retentionDays) &&
388
+ retentionDays >= 0
389
+ ) {
390
+ let payload: Record<string, unknown> = {};
391
+ try {
392
+ payload = JSON.parse(existing.payload) as Record<string, unknown>;
393
+ } catch {
394
+ payload = {};
395
+ }
396
+ if (payload.retentionDays !== retentionDays) {
397
+ db.update(memoryJobs)
398
+ .set({
399
+ payload: JSON.stringify({ ...payload, retentionDays }),
400
+ updatedAt: Date.now(),
401
+ })
402
+ .where(eq(memoryJobs.id, existing.id))
403
+ .run();
404
+ }
405
+ }
406
+ return existing.id;
407
+ }
408
+ const payload =
409
+ typeof retentionDays === "number" &&
410
+ Number.isFinite(retentionDays) &&
411
+ retentionDays >= 0
412
+ ? { retentionDays }
413
+ : {};
414
+ return enqueueMemoryJob("prune_old_trace_events", payload);
415
+ }
416
+
369
417
  export interface LaneBudgets {
370
418
  slowLlm: number;
371
419
  fast: number;
@@ -1,6 +1,10 @@
1
- import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
2
1
  import { getConfig } from "../config/loader.js";
3
2
  import type { AssistantConfig } from "../config/types.js";
3
+ import {
4
+ checkDiskPressureBackgroundGate,
5
+ diskPressureBackgroundSkipLogFields,
6
+ shouldLogDiskPressureBackgroundSkip,
7
+ } from "../daemon/disk-pressure-background-gate.js";
4
8
  import { getLogger } from "../util/logger.js";
5
9
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
6
10
  import {
@@ -23,6 +27,7 @@ import { backfillJob } from "./job-handlers/backfill.js";
23
27
  import {
24
28
  pruneOldConversationsJob,
25
29
  pruneOldLlmRequestLogsJob,
30
+ pruneOldTraceEventsJob,
26
31
  } from "./job-handlers/cleanup.js";
27
32
  import { generateConversationStartersJob } from "./job-handlers/conversation-starters.js";
28
33
  // ── Per-job-type handlers ──────────────────────────────────────────
@@ -54,6 +59,7 @@ import {
54
59
  enqueueMemoryJob,
55
60
  enqueuePruneOldConversationsJob,
56
61
  enqueuePruneOldLlmRequestLogsJob,
62
+ enqueuePruneOldTraceEventsJob,
57
63
  failMemoryJob,
58
64
  failStalledJobs,
59
65
  type MemoryJob,
@@ -167,6 +173,20 @@ export async function runMemoryJobsOnce(
167
173
  if (!config.memory.enabled) return 0;
168
174
  const enableScheduledCleanup = options.enableScheduledCleanup === true;
169
175
 
176
+ const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
177
+ if (diskPressureGate.action === "skip") {
178
+ if (shouldLogDiskPressureBackgroundSkip("memory-jobs-worker")) {
179
+ log.warn(
180
+ {
181
+ source: "memory",
182
+ ...diskPressureBackgroundSkipLogFields(diskPressureGate),
183
+ },
184
+ "Memory jobs worker skipped during disk pressure cleanup mode",
185
+ );
186
+ }
187
+ return 0;
188
+ }
189
+
170
190
  // Fail jobs that have been running longer than the configured timeout
171
191
  const timedOut = failStalledJobs(config.memory.jobs.stalledJobTimeoutMs);
172
192
  if (timedOut > 0) {
@@ -455,6 +475,9 @@ async function processJob(
455
475
  case "prune_old_llm_request_logs":
456
476
  pruneOldLlmRequestLogsJob(job, config);
457
477
  return;
478
+ case "prune_old_trace_events":
479
+ pruneOldTraceEventsJob(job, config);
480
+ return;
458
481
  case "build_conversation_summary":
459
482
  await buildConversationSummaryJob(job, config);
460
483
  return;
@@ -486,6 +509,11 @@ async function processJob(
486
509
  await embedGraphTriggerJob(job, config);
487
510
  return;
488
511
  case "graph_extract":
512
+ // Stale rows enqueued before v2 was enabled (or by any unguarded v1
513
+ // path) must not consume embedding/extraction budget when v2 is on.
514
+ if (config.memory.v2.enabled) {
515
+ return;
516
+ }
489
517
  await graphExtractJob(job, config);
490
518
  return;
491
519
  case "conversation_analyze":
@@ -560,14 +588,20 @@ function maybeEnqueueScheduledCleanupJobs(
560
588
  cleanup.llmRequestLogRetentionMs !== null
561
589
  ? enqueuePruneOldLlmRequestLogsJob(cleanup.llmRequestLogRetentionMs)
562
590
  : null;
591
+ const pruneTraceEventsJobId =
592
+ cleanup.traceEventRetentionDays > 0
593
+ ? enqueuePruneOldTraceEventsJob(cleanup.traceEventRetentionDays)
594
+ : null;
563
595
  markScheduledCleanupEnqueued(nowMs);
564
596
  log.debug(
565
597
  {
566
598
  pruneConversationsJobId,
567
599
  pruneLlmRequestLogsJobId,
600
+ pruneTraceEventsJobId,
568
601
  enqueueIntervalMs: cleanup.enqueueIntervalMs,
569
602
  conversationRetentionDays: cleanup.conversationRetentionDays,
570
603
  llmRequestLogRetentionMs: cleanup.llmRequestLogRetentionMs,
604
+ traceEventRetentionDays: cleanup.traceEventRetentionDays,
571
605
  },
572
606
  "Enqueued scheduled memory cleanup jobs",
573
607
  );
@@ -590,58 +624,66 @@ export const GRAPH_MAINTENANCE_CHECKPOINTS = {
590
624
  } as const;
591
625
 
592
626
  /**
593
- * Enqueue periodic graph maintenance jobs (decay, consolidation, pattern scan, narrative).
594
- * Uses durable checkpoints so intervals survive daemon restarts — jobs only fire
595
- * when the actual elapsed time since last run exceeds the interval.
627
+ * Enqueue periodic graph maintenance jobs.
596
628
  *
597
- * The v2 consolidation entry is gated on both the `memory-v2-enabled` feature
598
- * flag and the `memory.v2.enabled` config both must be true for the cron
599
- * to fire. Sweep is intentionally not on this schedule: it is debounced from
600
- * the live `graph_extract` trigger path (see `indexMessageNow` in
601
- * `indexer.ts`) so it runs on the same idle/message-count cadence.
629
+ * Mutually exclusive between v1 and v2:
630
+ * - v2 active (`memory.v2.enabled` on) only `memory_v2_consolidate` is
631
+ * scheduled.
632
+ * - v2 inactive → the four v1 entries (decay, consolidate, pattern_scan,
633
+ * narrative) are scheduled instead.
634
+ *
635
+ * Read/write paths route to v2 when the flag is on, so v1 graph data goes
636
+ * unread; running v1 maintenance alongside v2 is wasted compute and LLM
637
+ * spend. The v1 code path remains live so flipping the flag back to off
638
+ * fully re-engages v1.
639
+ *
640
+ * Uses durable checkpoints so intervals survive daemon restarts — jobs only
641
+ * fire when the actual elapsed time since last run exceeds the interval.
642
+ * Sweep is intentionally not on this schedule: it is debounced from the
643
+ * live `graph_extract` trigger path (see `indexMessageNow` in `indexer.ts`)
644
+ * so it runs on the same idle/message-count cadence.
602
645
  */
603
646
  export function maybeEnqueueGraphMaintenanceJobs(
604
647
  config: AssistantConfig,
605
648
  nowMs = Date.now(),
606
649
  ): void {
650
+ const v2Active = config.memory.v2.enabled;
651
+
607
652
  const schedule: Array<{
608
653
  key: string;
609
654
  intervalMs: number;
610
655
  jobType: MemoryJobType;
611
- }> = [
612
- {
613
- key: GRAPH_MAINTENANCE_CHECKPOINTS.decay,
614
- intervalMs: GRAPH_DECAY_INTERVAL_MS,
615
- jobType: "graph_decay",
616
- },
617
- {
618
- key: GRAPH_MAINTENANCE_CHECKPOINTS.consolidate,
619
- intervalMs: GRAPH_CONSOLIDATE_INTERVAL_MS,
620
- jobType: "graph_consolidate",
621
- },
622
- {
623
- key: GRAPH_MAINTENANCE_CHECKPOINTS.patternScan,
624
- intervalMs: GRAPH_PATTERN_SCAN_INTERVAL_MS,
625
- jobType: "graph_pattern_scan",
626
- },
627
- {
628
- key: GRAPH_MAINTENANCE_CHECKPOINTS.narrative,
629
- intervalMs: GRAPH_NARRATIVE_INTERVAL_MS,
630
- jobType: "graph_narrative_refine",
631
- },
632
- ];
633
-
634
- if (
635
- isAssistantFeatureFlagEnabled("memory-v2-enabled", config) &&
636
- config.memory.v2.enabled
637
- ) {
638
- schedule.push({
639
- key: GRAPH_MAINTENANCE_CHECKPOINTS.memoryV2Consolidate,
640
- intervalMs:
641
- config.memory.v2.consolidation_interval_hours * 60 * 60 * 1000,
642
- jobType: "memory_v2_consolidate",
643
- });
644
- }
656
+ }> = v2Active
657
+ ? [
658
+ {
659
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.memoryV2Consolidate,
660
+ intervalMs:
661
+ config.memory.v2.consolidation_interval_hours * 60 * 60 * 1000,
662
+ jobType: "memory_v2_consolidate",
663
+ },
664
+ ]
665
+ : [
666
+ {
667
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.decay,
668
+ intervalMs: GRAPH_DECAY_INTERVAL_MS,
669
+ jobType: "graph_decay",
670
+ },
671
+ {
672
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.consolidate,
673
+ intervalMs: GRAPH_CONSOLIDATE_INTERVAL_MS,
674
+ jobType: "graph_consolidate",
675
+ },
676
+ {
677
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.patternScan,
678
+ intervalMs: GRAPH_PATTERN_SCAN_INTERVAL_MS,
679
+ jobType: "graph_pattern_scan",
680
+ },
681
+ {
682
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.narrative,
683
+ intervalMs: GRAPH_NARRATIVE_INTERVAL_MS,
684
+ jobType: "graph_narrative_refine",
685
+ },
686
+ ];
645
687
 
646
688
  for (const { key, intervalMs, jobType } of schedule) {
647
689
  const lastRun = parseInt(getMemoryCheckpoint(key) ?? "0", 10);