@vellumai/assistant 0.5.16 → 0.6.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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. package/src/tools/memory/handlers.ts +0 -434
@@ -19,7 +19,7 @@ export type MemoryJobType =
19
19
  | "extract_items"
20
20
  | "extract_entities"
21
21
  | "batch_extract"
22
- | "cleanup_stale_superseded_items"
22
+ | "cleanup_stale_superseded_items" // legacy compat — silently dropped by worker (memory_items table dropped)
23
23
  | "prune_old_conversations"
24
24
  | "backfill_entity_relations"
25
25
  | "refresh_weekly_summary"
@@ -33,6 +33,14 @@ export type MemoryJobType =
33
33
  | "embed_attachment"
34
34
  | "generate_conversation_starters"
35
35
  | "journal_carry_forward"
36
+ | "embed_graph_node"
37
+ | "graph_extract"
38
+ | "graph_decay"
39
+ | "graph_consolidate"
40
+ | "graph_pattern_scan"
41
+ | "graph_narrative_refine"
42
+ | "graph_trigger_embed"
43
+ | "graph_bootstrap"
36
44
  | "generate_capability_cards" // legacy compat — silently dropped by worker (capability cards removed)
37
45
  | "generate_thread_starters"; // legacy compat — silently dropped by worker (renamed to generate_conversation_starters)
38
46
 
@@ -42,6 +50,8 @@ const EMBED_JOB_TYPES: MemoryJobType[] = [
42
50
  "embed_summary",
43
51
  "embed_media",
44
52
  "embed_attachment",
53
+ "embed_graph_node",
54
+ "graph_trigger_embed",
45
55
  ];
46
56
 
47
57
  export interface MemoryJob<T = Record<string, unknown>> {
@@ -63,10 +73,10 @@ export function enqueueMemoryJob(
63
73
  payload: Record<string, unknown>,
64
74
  runAfter = Date.now(),
65
75
  dbOverride?: Parameters<ReturnType<typeof getDb>["transaction"]>[0] extends (
66
- tx: infer T,
76
+ tx: infer T
67
77
  ) => unknown
68
78
  ? T
69
- : never,
79
+ : never
70
80
  ): string {
71
81
  const db = dbOverride ?? getDb();
72
82
  const id = uuid();
@@ -101,10 +111,10 @@ export function upsertDebouncedJob(
101
111
  payload: { conversationId: string },
102
112
  runAfter: number,
103
113
  dbOverride?: Parameters<ReturnType<typeof getDb>["transaction"]>[0] extends (
104
- tx: infer T,
114
+ tx: infer T
105
115
  ) => unknown
106
116
  ? T
107
- : never,
117
+ : never
108
118
  ): void {
109
119
  const db = dbOverride ?? getDb();
110
120
  const existing = db
@@ -114,8 +124,8 @@ export function upsertDebouncedJob(
114
124
  and(
115
125
  eq(memoryJobs.type, type),
116
126
  eq(memoryJobs.status, "pending"),
117
- sql`json_extract(${memoryJobs.payload}, '$.conversationId') = ${payload.conversationId}`,
118
- ),
127
+ sql`json_extract(${memoryJobs.payload}, '$.conversationId') = ${payload.conversationId}`
128
+ )
119
129
  )
120
130
  .get();
121
131
  if (existing) {
@@ -135,7 +145,7 @@ export function upsertDebouncedJob(
135
145
  */
136
146
  export function hasActiveCarryForwardJob(
137
147
  filename: string,
138
- userSlug: string,
148
+ userSlug: string
139
149
  ): boolean {
140
150
  const db = getDb();
141
151
  return (
@@ -147,65 +157,35 @@ export function hasActiveCarryForwardJob(
147
157
  eq(memoryJobs.type, "journal_carry_forward"),
148
158
  inArray(memoryJobs.status, ["pending", "running"]),
149
159
  sql`json_extract(${memoryJobs.payload}, '$.filename') = ${filename}`,
150
- sql`json_extract(${memoryJobs.payload}, '$.userSlug') = ${userSlug}`,
151
- ),
160
+ sql`json_extract(${memoryJobs.payload}, '$.userSlug') = ${userSlug}`
161
+ )
152
162
  )
153
163
  .get() != null
154
164
  );
155
165
  }
156
166
 
157
- export function enqueueCleanupStaleSupersededItemsJob(
158
- retentionMs?: number,
159
- ): string {
167
+ /**
168
+ * Check whether a pending or running job of the given type already exists.
169
+ * Used to prevent duplicate enqueues for long-running maintenance jobs.
170
+ */
171
+ export function hasActiveJobOfType(type: MemoryJobType): boolean {
160
172
  const db = getDb();
161
- const now = Date.now();
162
- const existing = db
163
- .select()
164
- .from(memoryJobs)
165
- .where(
166
- and(
167
- eq(memoryJobs.type, "cleanup_stale_superseded_items"),
168
- inArray(memoryJobs.status, ["pending", "running"]),
169
- ),
170
- )
171
- .orderBy(asc(memoryJobs.createdAt))
172
- .get();
173
- if (existing) {
174
- if (
175
- existing.status === "pending" &&
176
- typeof retentionMs === "number" &&
177
- Number.isFinite(retentionMs) &&
178
- retentionMs > 0
179
- ) {
180
- let payload: Record<string, unknown> = {};
181
- try {
182
- payload = JSON.parse(existing.payload) as Record<string, unknown>;
183
- } catch {
184
- payload = {};
185
- }
186
- if (payload.retentionMs !== retentionMs) {
187
- db.update(memoryJobs)
188
- .set({
189
- payload: JSON.stringify({ ...payload, retentionMs }),
190
- updatedAt: now,
191
- })
192
- .where(eq(memoryJobs.id, existing.id))
193
- .run();
194
- }
195
- }
196
- return existing.id;
197
- }
198
- const payload =
199
- typeof retentionMs === "number" &&
200
- Number.isFinite(retentionMs) &&
201
- retentionMs > 0
202
- ? { retentionMs }
203
- : {};
204
- return enqueueMemoryJob("cleanup_stale_superseded_items", payload);
173
+ return (
174
+ db
175
+ .select({ id: memoryJobs.id })
176
+ .from(memoryJobs)
177
+ .where(
178
+ and(
179
+ eq(memoryJobs.type, type),
180
+ inArray(memoryJobs.status, ["pending", "running"])
181
+ )
182
+ )
183
+ .get() != null
184
+ );
205
185
  }
206
186
 
207
187
  export function enqueuePruneOldConversationsJob(
208
- retentionDays?: number,
188
+ retentionDays?: number
209
189
  ): string {
210
190
  const db = getDb();
211
191
  const existing = db
@@ -214,8 +194,8 @@ export function enqueuePruneOldConversationsJob(
214
194
  .where(
215
195
  and(
216
196
  eq(memoryJobs.type, "prune_old_conversations"),
217
- inArray(memoryJobs.status, ["pending", "running"]),
218
- ),
197
+ inArray(memoryJobs.status, ["pending", "running"])
198
+ )
219
199
  )
220
200
  .orderBy(asc(memoryJobs.createdAt))
221
201
  .get();
@@ -259,7 +239,7 @@ export function claimMemoryJobs(limit: number): MemoryJob[] {
259
239
  const now = Date.now();
260
240
  const pendingFilter = and(
261
241
  eq(memoryJobs.status, "pending"),
262
- lte(memoryJobs.runAfter, now),
242
+ lte(memoryJobs.runAfter, now)
263
243
  );
264
244
 
265
245
  // Claim non-embed jobs first, then fill remaining slots with embed jobs.
@@ -288,7 +268,7 @@ export function claimMemoryJobs(limit: number): MemoryJob[] {
288
268
  }
289
269
  if (probeAllowed && remainingSlots > 0) {
290
270
  log.debug(
291
- "Allowing 1 embed probe job — Qdrant circuit breaker cooldown elapsed",
271
+ "Allowing 1 embed probe job — Qdrant circuit breaker cooldown elapsed"
292
272
  );
293
273
  }
294
274
 
@@ -318,7 +298,7 @@ export function claimMemoryJobs(limit: number): MemoryJob[] {
318
298
  status: "running",
319
299
  startedAt: now,
320
300
  updatedAt: now,
321
- }),
301
+ })
322
302
  );
323
303
  }
324
304
  return claimed;
@@ -363,7 +343,7 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
363
343
  if (deferrals >= MAX_DEFERRALS) {
364
344
  log.error(
365
345
  { jobId: id, type: row.type, deferrals },
366
- "Job exceeded max deferrals, marking as failed",
346
+ "Job exceeded max deferrals, marking as failed"
367
347
  );
368
348
  db.update(memoryJobs)
369
349
  .set({
@@ -382,14 +362,14 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
382
362
  if (DEFERRAL_WARN_MILESTONES.includes(deferrals)) {
383
363
  log.warn(
384
364
  { jobId: id, type: row.type, deferrals, max: MAX_DEFERRALS },
385
- "Job approaching max deferral limit",
365
+ "Job approaching max deferral limit"
386
366
  );
387
367
  }
388
368
 
389
369
  // Exponential backoff: 30s, 60s, 120s, ... capped at 5 minutes
390
370
  const delay = Math.min(
391
371
  DEFER_BASE_DELAY_MS * Math.pow(2, Math.min(deferrals - 1, 10)),
392
- DEFER_MAX_DELAY_MS,
372
+ DEFER_MAX_DELAY_MS
393
373
  );
394
374
  db.update(memoryJobs)
395
375
  .set({
@@ -406,7 +386,7 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
406
386
  export function failMemoryJob(
407
387
  id: string,
408
388
  error: string,
409
- options?: { retryDelayMs?: number; maxAttempts?: number },
389
+ options?: { retryDelayMs?: number; maxAttempts?: number }
410
390
  ): void {
411
391
  const retryDelayMs = options?.retryDelayMs ?? 30_000;
412
392
  const maxAttempts = options?.maxAttempts ?? 5;
@@ -463,7 +443,7 @@ export function failStalledJobs(timeoutMs: number): number {
463
443
  AND started_at IS NOT NULL
464
444
  AND started_at < ?
465
445
  `,
466
- cutoff,
446
+ cutoff
467
447
  );
468
448
  if (stalled.length === 0) return 0;
469
449
 
@@ -474,14 +454,14 @@ export function failStalledJobs(timeoutMs: number): number {
474
454
  status: "failed",
475
455
  updatedAt: now,
476
456
  lastError: `Job timed out after ${Math.round(
477
- timeoutMs / 60_000,
457
+ timeoutMs / 60_000
478
458
  )} minutes`,
479
459
  })
480
460
  .where(and(eq(memoryJobs.id, row.id), eq(memoryJobs.status, "running")))
481
461
  .run();
482
462
  log.warn(
483
463
  { jobId: row.id, type: row.type, timeoutMs },
484
- "Failed stalled memory job due to timeout",
464
+ "Failed stalled memory job due to timeout"
485
465
  );
486
466
  }
487
467
  return stalled.length;
@@ -1,29 +1,32 @@
1
1
  import { getConfig } from "../config/loader.js";
2
2
  import type { AssistantConfig } from "../config/types.js";
3
3
  import { getLogger } from "../util/logger.js";
4
- import { rawAll, rawRun } from "./db.js";
5
- import { backfillJob } from "./job-handlers/backfill.js";
6
- import { batchExtractJob } from "./job-handlers/batch-extraction.js";
4
+ import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
5
+ import { runConsolidation } from "./graph/consolidation.js";
6
+ import { runDecayTick } from "./graph/decay.js";
7
+ import { graphExtractJob } from "./graph/extraction-job.js";
7
8
  import {
8
- cleanupStaleSupersededItemsJob,
9
- pruneOldConversationsJob,
10
- } from "./job-handlers/cleanup.js";
9
+ embedGraphNodeJob,
10
+ embedGraphTriggerJob,
11
+ } from "./graph/graph-search.js";
12
+ import { runNarrativeRefinement } from "./graph/narrative.js";
13
+ import { runPatternScan } from "./graph/pattern-scan.js";
14
+ import { backfillJob } from "./job-handlers/backfill.js";
15
+ import { pruneOldConversationsJob } from "./job-handlers/cleanup.js";
11
16
  import { generateConversationStartersJob } from "./job-handlers/conversation-starters.js";
12
17
  // ── Per-job-type handlers ──────────────────────────────────────────
13
18
  import {
14
19
  embedAttachmentJob,
15
- embedItemJob,
16
20
  embedMediaJob,
17
21
  embedSegmentJob,
18
22
  embedSummaryJob,
19
23
  } from "./job-handlers/embedding.js";
20
- import { extractItemsJob } from "./job-handlers/extraction.js";
21
24
  import {
22
25
  deleteQdrantVectorsJob,
23
26
  rebuildIndexJob,
24
27
  } from "./job-handlers/index-maintenance.js";
25
- import { journalCarryForwardJob } from "./job-handlers/journal-carry-forward.js";
26
28
  import { mediaProcessingJob } from "./job-handlers/media-processing.js";
29
+ import { buildConversationSummaryJob } from "./job-handlers/summarization.js";
27
30
  import {
28
31
  BackendUnavailableError,
29
32
  classifyError,
@@ -34,12 +37,12 @@ import {
34
37
  claimMemoryJobs,
35
38
  completeMemoryJob,
36
39
  deferMemoryJob,
37
- enqueueCleanupStaleSupersededItemsJob,
38
40
  enqueueMemoryJob,
39
41
  enqueuePruneOldConversationsJob,
40
42
  failMemoryJob,
41
43
  failStalledJobs,
42
44
  type MemoryJob,
45
+ type MemoryJobType,
43
46
  resetRunningJobsToPending,
44
47
  } from "./jobs-store.js";
45
48
  import { QdrantCircuitOpenError } from "./qdrant-circuit-breaker.js";
@@ -60,31 +63,6 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
60
63
  log.info({ recovered }, "Recovered stale running memory jobs");
61
64
  }
62
65
 
63
- // Startup recovery: enqueue batch_extract for conversations with pending
64
- // unextracted messages (e.g. after a crash mid-conversation).
65
- try {
66
- const pendingRows = rawAll<{ key: string; value: string }>(
67
- `SELECT key, value FROM memory_checkpoints WHERE key LIKE 'batch_extract:%:pending_count' AND CAST(value AS INTEGER) > 0`,
68
- );
69
- for (const row of pendingRows) {
70
- // Extract conversationId from key: "batch_extract:<conversationId>:pending_count"
71
- const parts = row.key.split(":");
72
- if (parts.length >= 3) {
73
- const conversationId = parts.slice(1, -1).join(":");
74
- enqueueMemoryJob("batch_extract", { conversationId });
75
- log.info(
76
- { conversationId, pendingCount: row.value },
77
- "Recovered pending batch extraction on startup",
78
- );
79
- }
80
- }
81
- } catch (err) {
82
- log.warn(
83
- { err: err instanceof Error ? err.message : String(err) },
84
- "Failed to recover pending batch extractions on startup",
85
- );
86
- }
87
-
88
66
  let stopped = false;
89
67
  let tickRunning = false;
90
68
  let timer: ReturnType<typeof setTimeout>;
@@ -145,9 +123,6 @@ export async function runMemoryJobsOnce(
145
123
  if (!config.memory.enabled) return 0;
146
124
  const enableScheduledCleanup = options.enableScheduledCleanup === true;
147
125
 
148
- // Periodic stale item sweep (throttled to at most once per hour)
149
- sweepStaleItems(config);
150
-
151
126
  // Fail jobs that have been running longer than the configured timeout
152
127
  const timedOut = failStalledJobs(config.memory.jobs.stalledJobTimeoutMs);
153
128
  if (timedOut > 0) {
@@ -161,6 +136,7 @@ export async function runMemoryJobsOnce(
161
136
  if (enableScheduledCleanup) {
162
137
  maybeEnqueueScheduledCleanupJobs(config);
163
138
  }
139
+ maybeEnqueueGraphMaintenanceJobs();
164
140
  return 0;
165
141
  }
166
142
 
@@ -256,9 +232,67 @@ export async function runMemoryJobsOnce(
256
232
  if (enableScheduledCleanup) {
257
233
  maybeEnqueueScheduledCleanupJobs(config);
258
234
  }
235
+ maybeEnqueueGraphMaintenanceJobs();
259
236
  return processed;
260
237
  }
261
238
 
239
+ // ── Graph lifecycle job handlers ──────────────────────────────────
240
+
241
+ function graphDecayJob(job: MemoryJob): void {
242
+ const scopeId = (job.payload as { scopeId?: string })?.scopeId ?? "default";
243
+ const result = runDecayTick(scopeId);
244
+ log.info({ jobId: job.id, ...result }, "Graph decay tick complete");
245
+ }
246
+
247
+ async function graphConsolidateJob(
248
+ job: MemoryJob,
249
+ config: AssistantConfig,
250
+ ): Promise<void> {
251
+ const scopeId = (job.payload as { scopeId?: string })?.scopeId ?? "default";
252
+ const result = await runConsolidation(scopeId, config);
253
+ log.info(
254
+ {
255
+ jobId: job.id,
256
+ updated: result.totalUpdated,
257
+ deleted: result.totalDeleted,
258
+ mergeEdges: result.totalMergeEdges,
259
+ },
260
+ "Graph consolidation complete",
261
+ );
262
+ }
263
+
264
+ async function graphPatternScanJob(
265
+ job: MemoryJob,
266
+ config: AssistantConfig,
267
+ ): Promise<void> {
268
+ const scopeId = (job.payload as { scopeId?: string })?.scopeId ?? "default";
269
+ const result = await runPatternScan(scopeId, config);
270
+ log.info(
271
+ {
272
+ jobId: job.id,
273
+ patterns: result.patternsDetected,
274
+ edges: result.edgesCreated,
275
+ },
276
+ "Graph pattern scan complete",
277
+ );
278
+ }
279
+
280
+ async function graphNarrativeRefineJob(
281
+ job: MemoryJob,
282
+ config: AssistantConfig,
283
+ ): Promise<void> {
284
+ const scopeId = (job.payload as { scopeId?: string })?.scopeId ?? "default";
285
+ const result = await runNarrativeRefinement(scopeId, config);
286
+ log.info(
287
+ {
288
+ jobId: job.id,
289
+ updated: result.nodesUpdated,
290
+ arcs: result.arcsIdentified,
291
+ },
292
+ "Graph narrative refinement complete",
293
+ );
294
+ }
295
+
262
296
  // ── Job error handling ─────────────────────────────────────────────
263
297
 
264
298
  function handleJobError(job: MemoryJob, err: unknown): void {
@@ -322,33 +356,20 @@ async function processJob(
322
356
  await embedSegmentJob(job, config);
323
357
  return;
324
358
  case "embed_item":
325
- await embedItemJob(job, config);
326
- return;
327
- case "embed_summary":
328
- await embedSummaryJob(job, config);
329
- return;
330
359
  case "extract_items":
331
- await extractItemsJob(job);
332
- return;
333
360
  case "batch_extract":
334
- await batchExtractJob(job);
335
- return;
336
361
  case "extract_entities":
337
- // Entity extraction has been removed — silently drop legacy jobs
338
- return;
339
362
  case "cleanup_stale_superseded_items":
340
- cleanupStaleSupersededItemsJob(job, config);
363
+ // Old extraction pipeline replaced by memory graph — silently drop
364
+ return;
365
+ case "embed_summary":
366
+ await embedSummaryJob(job, config);
341
367
  return;
342
368
  case "prune_old_conversations":
343
369
  pruneOldConversationsJob(job, config);
344
370
  return;
345
371
  case "build_conversation_summary":
346
- // Deprecated: conversation summaries are now produced as a side-effect
347
- // of batch extraction. Silently skip legacy jobs.
348
- log.debug(
349
- { jobId: job.id },
350
- "Skipping deprecated build_conversation_summary job — handled by batch extraction",
351
- );
372
+ await buildConversationSummaryJob(job, config);
352
373
  return;
353
374
  case "backfill":
354
375
  await backfillJob(job, config);
@@ -376,7 +397,28 @@ async function processJob(
376
397
  await embedAttachmentJob(job, config);
377
398
  return;
378
399
  case "journal_carry_forward":
379
- await journalCarryForwardJob(job);
400
+ // Journal carry-forward replaced by graph extraction — silently drop
401
+ return;
402
+ case "embed_graph_node":
403
+ await embedGraphNodeJob(job, config);
404
+ return;
405
+ case "graph_trigger_embed":
406
+ await embedGraphTriggerJob(job, config);
407
+ return;
408
+ case "graph_extract":
409
+ await graphExtractJob(job, config);
410
+ return;
411
+ case "graph_decay":
412
+ graphDecayJob(job);
413
+ return;
414
+ case "graph_consolidate":
415
+ await graphConsolidateJob(job, config);
416
+ return;
417
+ case "graph_pattern_scan":
418
+ await graphPatternScanJob(job, config);
419
+ return;
420
+ case "graph_narrative_refine":
421
+ await graphNarrativeRefineJob(job, config);
380
422
  return;
381
423
  case "generate_conversation_starters":
382
424
  await generateConversationStartersJob(job);
@@ -416,9 +458,6 @@ export function maybeEnqueueScheduledCleanupJobs(
416
458
  if (nowMs - lastScheduledCleanupEnqueueMs < cleanup.enqueueIntervalMs)
417
459
  return false;
418
460
 
419
- const staleSupersededItemsJobId = enqueueCleanupStaleSupersededItemsJob(
420
- cleanup.supersededItemRetentionMs,
421
- );
422
461
  const pruneConversationsJobId =
423
462
  cleanup.conversationRetentionDays > 0
424
463
  ? enqueuePruneOldConversationsJob(cleanup.conversationRetentionDays)
@@ -426,10 +465,8 @@ export function maybeEnqueueScheduledCleanupJobs(
426
465
  lastScheduledCleanupEnqueueMs = nowMs;
427
466
  log.debug(
428
467
  {
429
- staleSupersededItemsJobId,
430
468
  pruneConversationsJobId,
431
469
  enqueueIntervalMs: cleanup.enqueueIntervalMs,
432
- supersededItemRetentionMs: cleanup.supersededItemRetentionMs,
433
470
  conversationRetentionDays: cleanup.conversationRetentionDays,
434
471
  },
435
472
  "Enqueued scheduled memory cleanup jobs",
@@ -437,61 +474,59 @@ export function maybeEnqueueScheduledCleanupJobs(
437
474
  return true;
438
475
  }
439
476
 
440
- // ── Stale item sweep ───────────────────────────────────────────────
477
+ // ── Graph maintenance scheduling ──────────────────────────────────
441
478
 
442
- const STALE_SWEEP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
443
- let lastStaleSweepMs = 0;
479
+ const GRAPH_DECAY_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
480
+ const GRAPH_CONSOLIDATE_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
481
+ const GRAPH_PATTERN_SCAN_INTERVAL_MS = 24 * 60 * 60 * 1000; // 1 day
482
+ const GRAPH_NARRATIVE_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week
444
483
 
445
- /** Reset the sweep throttle so tests can call sweepStaleItems back-to-back. */
446
- export function resetStaleSweepThrottle(): void {
447
- lastStaleSweepMs = 0;
448
- }
484
+ const GRAPH_MAINTENANCE_CHECKPOINTS = {
485
+ decay: "graph_maintenance:decay:last_run",
486
+ consolidate: "graph_maintenance:consolidate:last_run",
487
+ patternScan: "graph_maintenance:pattern_scan:last_run",
488
+ narrative: "graph_maintenance:narrative:last_run",
489
+ } as const;
449
490
 
450
491
  /**
451
- * Mark deeply stale memory items as invalid. An item is considered deeply
452
- * stale when it has exceeded 2x its freshness window for its kind and has
453
- * not been recently accessed.
454
- *
455
- * This is non-destructive: items keep their data but get an `invalid_at`
456
- * timestamp that excludes them from retrieval queries.
492
+ * Enqueue periodic graph maintenance jobs (decay, consolidation, pattern scan, narrative).
493
+ * Uses durable checkpoints so intervals survive daemon restarts jobs only fire
494
+ * when the actual elapsed time since last run exceeds the interval.
457
495
  */
458
- export function sweepStaleItems(config: AssistantConfig): number {
459
- const freshness = config.memory.retrieval.freshness;
460
- if (!freshness.enabled) return 0;
461
-
462
- const now = Date.now();
463
- // Throttle: at most once per hour
464
- if (now - lastStaleSweepMs < STALE_SWEEP_INTERVAL_MS) return 0;
465
- lastStaleSweepMs = now;
466
-
467
- let totalMarked = 0;
468
- for (const [kind, maxAgeDays] of Object.entries(freshness.maxAgeDays)) {
469
- if (maxAgeDays <= 0) continue;
470
- // Mark invalid if: past 2x window, no access in the shield period, and not already invalid
471
- const cutoffMs = now - maxAgeDays * 2 * 86_400_000;
472
- const shieldCutoffMs = now - freshness.reinforcementShieldDays * 86_400_000;
473
- const changes = rawRun(
474
- `
475
- UPDATE memory_items
476
- SET invalid_at = ?
477
- WHERE kind = ?
478
- AND status = 'active'
479
- AND invalid_at IS NULL
480
- AND last_seen_at < ?
481
- AND (access_count = 0 OR COALESCE(last_used_at, 0) < ?)
482
- `,
483
- now,
484
- kind,
485
- cutoffMs,
486
- shieldCutoffMs,
487
- );
488
- if (changes > 0) {
489
- log.info(
490
- { kind, marked: changes, cutoffMs },
491
- "Marked stale memory items as invalid",
492
- );
493
- totalMarked += changes;
496
+ function maybeEnqueueGraphMaintenanceJobs(nowMs = Date.now()): void {
497
+ const schedule: Array<{
498
+ key: string;
499
+ intervalMs: number;
500
+ jobType: MemoryJobType;
501
+ }> = [
502
+ {
503
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.decay,
504
+ intervalMs: GRAPH_DECAY_INTERVAL_MS,
505
+ jobType: "graph_decay",
506
+ },
507
+ {
508
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.consolidate,
509
+ intervalMs: GRAPH_CONSOLIDATE_INTERVAL_MS,
510
+ jobType: "graph_consolidate",
511
+ },
512
+ {
513
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.patternScan,
514
+ intervalMs: GRAPH_PATTERN_SCAN_INTERVAL_MS,
515
+ jobType: "graph_pattern_scan",
516
+ },
517
+ {
518
+ key: GRAPH_MAINTENANCE_CHECKPOINTS.narrative,
519
+ intervalMs: GRAPH_NARRATIVE_INTERVAL_MS,
520
+ jobType: "graph_narrative_refine",
521
+ },
522
+ ];
523
+
524
+ for (const { key, intervalMs, jobType } of schedule) {
525
+ const lastRun = parseInt(getMemoryCheckpoint(key) ?? "0", 10);
526
+ if (nowMs - lastRun >= intervalMs) {
527
+ enqueueMemoryJob(jobType, {});
528
+ setMemoryCheckpoint(key, String(nowMs));
494
529
  }
495
530
  }
496
- return totalMarked;
497
531
  }
532
+
@@ -121,6 +121,7 @@ export function extractMediaBlockMeta(
121
121
  }
122
122
  }
123
123
 
124
+
124
125
  function stableJson(value: unknown): string {
125
126
  try {
126
127
  return JSON.stringify(value);