@vellumai/assistant 0.6.0 → 0.6.1

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 (285) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/openapi.yaml +538 -3
  9. package/package.json +5 -1
  10. package/src/__tests__/anthropic-provider.test.ts +160 -95
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +47 -1
  13. package/src/__tests__/app-source-watcher.test.ts +159 -0
  14. package/src/__tests__/checker.test.ts +38 -6
  15. package/src/__tests__/config-schema.test.ts +5 -0
  16. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  17. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  18. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  19. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  20. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  21. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  22. package/src/__tests__/conversation-wipe.test.ts +2 -6
  23. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  24. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  25. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  26. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  27. package/src/__tests__/date-context.test.ts +76 -210
  28. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  29. package/src/__tests__/file-list-tool.test.ts +219 -0
  30. package/src/__tests__/first-greeting.test.ts +1 -1
  31. package/src/__tests__/heartbeat-service.test.ts +180 -3
  32. package/src/__tests__/identity-routes.test.ts +328 -0
  33. package/src/__tests__/injection-block.test.ts +24 -0
  34. package/src/__tests__/install-skill-routing.test.ts +7 -6
  35. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  36. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  37. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  38. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  39. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  40. package/src/__tests__/log-export-workspace.test.ts +72 -105
  41. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  42. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  43. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  44. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  45. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  46. package/src/__tests__/mock-fetch.ts +87 -0
  47. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  48. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  49. package/src/__tests__/parser.test.ts +32 -0
  50. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  51. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  52. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  53. package/src/__tests__/permission-mode-store.test.ts +277 -0
  54. package/src/__tests__/permission-mode.test.ts +101 -0
  55. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  56. package/src/__tests__/profiler-routes.test.ts +502 -0
  57. package/src/__tests__/profiler-run-store.test.ts +441 -0
  58. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  59. package/src/__tests__/registry.test.ts +1 -1
  60. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  61. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  62. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  63. package/src/__tests__/search-skills-unified.test.ts +4 -3
  64. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  65. package/src/__tests__/set-permission-mode.test.ts +274 -0
  66. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  67. package/src/__tests__/skill-memory.test.ts +2 -783
  68. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  69. package/src/__tests__/subagent-detail.test.ts +84 -0
  70. package/src/__tests__/subagent-disposal.test.ts +308 -0
  71. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  72. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  73. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  74. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  75. package/src/__tests__/subagent-tools.test.ts +464 -4
  76. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  77. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  78. package/src/__tests__/terminal-tools.test.ts +17 -27
  79. package/src/__tests__/test-preload.ts +4 -0
  80. package/src/__tests__/tool-executor.test.ts +4 -26
  81. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  82. package/src/__tests__/top-level-renderer.test.ts +10 -13
  83. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  84. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  85. package/src/agent/loop.ts +6 -0
  86. package/src/approvals/guardian-request-resolvers.ts +24 -0
  87. package/src/avatar/traits-png-sync.ts +3 -3
  88. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  89. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  90. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  91. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  92. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  93. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  94. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  95. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  96. package/src/cli/commands/conversations.ts +1 -8
  97. package/src/cli/commands/email.ts +584 -835
  98. package/src/cli/commands/memory.ts +1 -34
  99. package/src/cli/commands/notifications.ts +7 -2
  100. package/src/cli/commands/oauth/connect.ts +14 -5
  101. package/src/cli/commands/routes.ts +396 -0
  102. package/src/cli/commands/skills.ts +130 -20
  103. package/src/cli/program.ts +2 -0
  104. package/src/cli.ts +1 -120
  105. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  106. package/src/config/bundled-skills/gmail/SKILL.md +2 -2
  107. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  108. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  109. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  111. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  112. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  113. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  114. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  115. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  116. package/src/config/env-registry.ts +63 -0
  117. package/src/config/feature-flag-registry.json +17 -1
  118. package/src/config/schema.ts +8 -0
  119. package/src/config/schemas/filing.ts +51 -0
  120. package/src/config/schemas/heartbeat.ts +15 -12
  121. package/src/config/schemas/memory-lifecycle.ts +12 -0
  122. package/src/config/schemas/security.ts +14 -0
  123. package/src/daemon/app-source-watcher.ts +93 -0
  124. package/src/daemon/config-watcher.ts +79 -1
  125. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  126. package/src/daemon/conversation-agent-loop.ts +158 -65
  127. package/src/daemon/conversation-history.ts +4 -19
  128. package/src/daemon/conversation-lifecycle.ts +8 -14
  129. package/src/daemon/conversation-process.ts +13 -7
  130. package/src/daemon/conversation-runtime-assembly.ts +300 -306
  131. package/src/daemon/conversation-tool-setup.ts +44 -14
  132. package/src/daemon/conversation-workspace.ts +1 -2
  133. package/src/daemon/conversation.ts +18 -0
  134. package/src/daemon/date-context.ts +26 -53
  135. package/src/daemon/first-greeting.ts +1 -1
  136. package/src/daemon/handlers/conversations.ts +4 -7
  137. package/src/daemon/handlers/shared.test.ts +143 -0
  138. package/src/daemon/handlers/shared.ts +63 -5
  139. package/src/daemon/handlers/skills.ts +11 -18
  140. package/src/daemon/lifecycle.ts +199 -157
  141. package/src/daemon/message-types/conversations.ts +25 -6
  142. package/src/daemon/message-types/messages.ts +9 -1
  143. package/src/daemon/message-types/schedules.ts +1 -0
  144. package/src/daemon/message-types/settings.ts +6 -0
  145. package/src/daemon/profiler-run-store.ts +557 -0
  146. package/src/daemon/server.ts +89 -9
  147. package/src/daemon/shutdown-handlers.ts +5 -0
  148. package/src/daemon/tool-side-effects.ts +23 -3
  149. package/src/export/transcript-formatter.ts +148 -0
  150. package/src/filing/filing-service.ts +228 -0
  151. package/src/heartbeat/heartbeat-service.ts +96 -7
  152. package/src/mcp/client.ts +6 -0
  153. package/src/mcp/mcp-oauth-provider.ts +149 -27
  154. package/src/memory/admin.ts +33 -32
  155. package/src/memory/app-store.ts +69 -0
  156. package/src/memory/conversation-bootstrap.ts +1 -1
  157. package/src/memory/conversation-crud.ts +136 -107
  158. package/src/memory/conversation-group-migration.ts +1 -1
  159. package/src/memory/conversation-queries.ts +58 -12
  160. package/src/memory/conversation-title-service.ts +1 -0
  161. package/src/memory/db-init.ts +182 -376
  162. package/src/memory/graph/bootstrap.ts +75 -66
  163. package/src/memory/graph/capability-seed.ts +167 -15
  164. package/src/memory/graph/consolidation.ts +38 -4
  165. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  166. package/src/memory/graph/extraction-job.ts +9 -4
  167. package/src/memory/graph/extraction.ts +66 -23
  168. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  169. package/src/memory/graph/graph-search.ts +29 -15
  170. package/src/memory/graph/injection.ts +38 -8
  171. package/src/memory/graph/inspect.ts +12 -3
  172. package/src/memory/graph/retriever.ts +365 -262
  173. package/src/memory/graph/store.test.ts +48 -0
  174. package/src/memory/graph/store.ts +150 -11
  175. package/src/memory/graph/tool-handlers.ts +84 -209
  176. package/src/memory/graph/tools.ts +8 -52
  177. package/src/memory/graph/types.ts +24 -0
  178. package/src/memory/job-handlers/cleanup.ts +44 -1
  179. package/src/memory/jobs-store.ts +70 -60
  180. package/src/memory/jobs-worker.ts +44 -28
  181. package/src/memory/llm-request-log-store.ts +96 -12
  182. package/src/memory/memory-recall-log-store.ts +49 -5
  183. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  184. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  185. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  186. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  187. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  188. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  189. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  190. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  191. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  192. package/src/memory/migrations/index.ts +8 -0
  193. package/src/memory/migrations/registry.ts +8 -0
  194. package/src/memory/schema/conversations.ts +14 -0
  195. package/src/memory/schema/infrastructure.ts +8 -1
  196. package/src/memory/schema/memory-core.ts +0 -51
  197. package/src/memory/schema/memory-graph.ts +15 -0
  198. package/src/memory/task-memory-cleanup.ts +30 -11
  199. package/src/notifications/copy-composer.ts +86 -0
  200. package/src/notifications/decision-engine.ts +35 -0
  201. package/src/permissions/checker.ts +12 -1
  202. package/src/permissions/permission-mode-store.ts +180 -0
  203. package/src/permissions/permission-mode.ts +31 -0
  204. package/src/permissions/workspace-policy.ts +9 -0
  205. package/src/prompts/system-prompt.ts +59 -7
  206. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  207. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  208. package/src/prompts/templates/HEARTBEAT.md +3 -1
  209. package/src/prompts/templates/SOUL.md +25 -4
  210. package/src/prompts/templates/UPDATES.md +8 -0
  211. package/src/providers/anthropic/client.ts +107 -219
  212. package/src/runtime/auth/route-policy.ts +23 -0
  213. package/src/runtime/http-server.ts +32 -2
  214. package/src/runtime/http-types.ts +12 -1
  215. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  216. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  217. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  218. package/src/runtime/routes/app-management-routes.ts +1 -11
  219. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  220. package/src/runtime/routes/archive-utils.ts +29 -0
  221. package/src/runtime/routes/avatar-routes.ts +2 -9
  222. package/src/runtime/routes/btw-routes.ts +14 -1
  223. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  224. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  225. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  226. package/src/runtime/routes/conversation-routes.ts +264 -44
  227. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  228. package/src/runtime/routes/identity-routes.ts +53 -18
  229. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  230. package/src/runtime/routes/log-export-routes.ts +23 -275
  231. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  232. package/src/runtime/routes/migration-routes.ts +18 -7
  233. package/src/runtime/routes/profiler-routes.ts +350 -0
  234. package/src/runtime/routes/schedule-routes.ts +27 -12
  235. package/src/runtime/routes/settings-routes.ts +95 -8
  236. package/src/runtime/routes/subagents-routes.ts +28 -7
  237. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  238. package/src/runtime/routes/user-routes.ts +41 -0
  239. package/src/runtime/routes/workspace-routes.ts +0 -1
  240. package/src/schedule/schedule-store.ts +30 -0
  241. package/src/schedule/scheduler.ts +45 -18
  242. package/src/skills/catalog-install.ts +10 -2
  243. package/src/skills/managed-store.ts +2 -2
  244. package/src/skills/skill-memory.ts +1 -293
  245. package/src/subagent/index.ts +13 -3
  246. package/src/subagent/manager.ts +308 -29
  247. package/src/subagent/types.ts +68 -0
  248. package/src/tasks/task-runner.ts +4 -4
  249. package/src/tools/apps/executors.ts +29 -4
  250. package/src/tools/filesystem/list.ts +93 -0
  251. package/src/tools/permission-checker.ts +78 -0
  252. package/src/tools/registry.ts +4 -0
  253. package/src/tools/schedule/create.ts +3 -0
  254. package/src/tools/schedule/list.ts +1 -0
  255. package/src/tools/schedule/update.ts +6 -0
  256. package/src/tools/shared/filesystem/errors.ts +5 -0
  257. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  258. package/src/tools/shared/filesystem/types.ts +17 -0
  259. package/src/tools/shared/shell-output.ts +31 -2
  260. package/src/tools/subagent/abort.ts +12 -2
  261. package/src/tools/subagent/message.ts +9 -2
  262. package/src/tools/subagent/notify-parent.ts +79 -0
  263. package/src/tools/subagent/read.ts +29 -8
  264. package/src/tools/subagent/resolve.ts +21 -0
  265. package/src/tools/subagent/spawn.ts +2 -0
  266. package/src/tools/subagent/status.ts +11 -1
  267. package/src/tools/system/avatar-generator.ts +3 -3
  268. package/src/tools/system/register.ts +23 -0
  269. package/src/tools/system/set-permission-mode.ts +103 -0
  270. package/src/tools/terminal/parser.ts +30 -5
  271. package/src/tools/terminal/safe-env.ts +16 -1
  272. package/src/tools/tool-manifest.ts +6 -0
  273. package/src/tools/types.ts +2 -0
  274. package/src/util/logger.ts +1 -1
  275. package/src/util/platform.ts +50 -17
  276. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  277. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  278. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  279. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  280. package/src/workspace/migrations/registry.ts +4 -0
  281. package/src/workspace/top-level-renderer.ts +5 -9
  282. package/src/__tests__/cli-memory.test.ts +0 -377
  283. package/src/__tests__/clipboard.test.ts +0 -88
  284. package/src/cli/cli-memory.ts +0 -179
  285. package/src/util/clipboard.ts +0 -34
@@ -62,14 +62,10 @@ mock.module("../../memory/qdrant-circuit-breaker.js", () => ({
62
62
  withQdrantBreaker: async (fn: () => Promise<unknown>) => fn(),
63
63
  }));
64
64
 
65
- import { and, eq } from "drizzle-orm";
65
+ import { eq } from "drizzle-orm";
66
66
 
67
67
  import { getDb, initializeDb } from "../../memory/db.js";
68
- import {
69
- memoryEmbeddings,
70
- memoryItems,
71
- memoryJobs,
72
- } from "../../memory/schema.js";
68
+ import { memoryGraphNodes, memoryJobs } from "../../memory/schema.js";
73
69
  import type { RouteContext } from "../http-router.js";
74
70
  import { memoryItemRouteDefinitions } from "./memory-item-routes.js";
75
71
 
@@ -125,42 +121,45 @@ function makeJsonCtx(
125
121
 
126
122
  function insertItem(opts: {
127
123
  id: string;
128
- kind: string;
129
- subject: string;
130
- statement: string;
131
- status?: string;
132
- importance?: number;
133
- firstSeenAt?: number;
134
- lastSeenAt?: number;
135
- supersedes?: string;
136
- supersededBy?: string;
124
+ type: string;
125
+ content: string;
126
+ fidelity?: string;
127
+ significance?: number;
128
+ created?: number;
129
+ lastAccessed?: number;
137
130
  }) {
138
131
  const db = getDb();
139
132
  const now = Date.now();
140
- db.insert(memoryItems)
133
+ db.insert(memoryGraphNodes)
141
134
  .values({
142
135
  id: opts.id,
143
- kind: opts.kind,
144
- subject: opts.subject,
145
- statement: opts.statement,
146
- status: opts.status ?? "active",
136
+ content: opts.content,
137
+ type: opts.type,
138
+ created: opts.created ?? now,
139
+ lastAccessed: opts.lastAccessed ?? now,
140
+ lastConsolidated: now,
141
+ eventDate: null,
142
+ emotionalCharge: JSON.stringify({
143
+ valence: 0,
144
+ intensity: 0.1,
145
+ decayCurve: "linear",
146
+ decayRate: 0.05,
147
+ originalIntensity: 0.1,
148
+ }),
149
+ fidelity: opts.fidelity ?? "vivid",
147
150
  confidence: 0.95,
148
- importance: opts.importance ?? 0.8,
149
- fingerprint: `fp-${opts.id}`,
150
- verificationState: "user_confirmed",
151
+ significance: opts.significance ?? 0.8,
152
+ stability: 14,
153
+ reinforcementCount: 0,
154
+ lastReinforced: now,
155
+ sourceConversations: JSON.stringify([]),
156
+ sourceType: "direct",
157
+ narrativeRole: null,
158
+ partOfStory: null,
159
+ imageRefs: null,
151
160
  scopeId: "default",
152
- firstSeenAt: opts.firstSeenAt ?? now,
153
- lastSeenAt: opts.lastSeenAt ?? now,
154
- lastUsedAt: null,
155
161
  })
156
162
  .run();
157
-
158
- if (opts.supersedes || opts.supersededBy) {
159
- const set: Record<string, unknown> = {};
160
- if (opts.supersedes) set.supersedes = opts.supersedes;
161
- if (opts.supersededBy) set.supersededBy = opts.supersededBy;
162
- db.update(memoryItems).set(set).where(eq(memoryItems.id, opts.id)).run();
163
- }
164
163
  }
165
164
 
166
165
  // ---------------------------------------------------------------------------
@@ -174,9 +173,10 @@ describe("Memory Item Routes", () => {
174
173
 
175
174
  beforeEach(() => {
176
175
  const db = getDb();
177
- db.run("DELETE FROM memory_embeddings");
178
- db.run("DELETE FROM memory_item_sources");
179
- db.run("DELETE FROM memory_items");
176
+ db.run("DELETE FROM memory_graph_node_edits");
177
+ db.run("DELETE FROM memory_graph_triggers");
178
+ db.run("DELETE FROM memory_graph_edges");
179
+ db.run("DELETE FROM memory_graph_nodes");
180
180
  db.run("DELETE FROM memory_jobs");
181
181
  });
182
182
 
@@ -199,16 +199,14 @@ describe("Memory Item Routes", () => {
199
199
  test("returns all active items by default", async () => {
200
200
  insertItem({
201
201
  id: "i1",
202
- kind: "preference",
203
- subject: "s1",
204
- statement: "st1",
202
+ type: "semantic",
203
+ content: "s1\nst1",
205
204
  });
206
205
  insertItem({
207
206
  id: "i2",
208
- kind: "identity",
209
- subject: "s2",
210
- statement: "st2",
211
- status: "deleted",
207
+ type: "episodic",
208
+ content: "s2\nst2",
209
+ fidelity: "gone",
212
210
  });
213
211
 
214
212
  const ctx = makeCtx();
@@ -226,17 +224,15 @@ describe("Memory Item Routes", () => {
226
224
  test("returns items of all statuses when status=all", async () => {
227
225
  insertItem({
228
226
  id: "i1",
229
- kind: "preference",
230
- subject: "s1",
231
- statement: "st1",
232
- status: "active",
227
+ type: "semantic",
228
+ content: "s1\nst1",
229
+ fidelity: "vivid",
233
230
  });
234
231
  insertItem({
235
232
  id: "i2",
236
- kind: "identity",
237
- subject: "s2",
238
- statement: "st2",
239
- status: "deleted",
233
+ type: "episodic",
234
+ content: "s2\nst2",
235
+ fidelity: "gone",
240
236
  });
241
237
 
242
238
  const ctx = makeCtx({ status: "all" });
@@ -255,18 +251,16 @@ describe("Memory Item Routes", () => {
255
251
  test("filters by kind", async () => {
256
252
  insertItem({
257
253
  id: "i1",
258
- kind: "preference",
259
- subject: "s1",
260
- statement: "st1",
254
+ type: "semantic",
255
+ content: "s1\nst1",
261
256
  });
262
257
  insertItem({
263
258
  id: "i2",
264
- kind: "identity",
265
- subject: "s2",
266
- statement: "st2",
259
+ type: "episodic",
260
+ content: "s2\nst2",
267
261
  });
268
262
 
269
- const ctx = makeCtx({ kind: "preference" });
263
+ const ctx = makeCtx({ kind: "semantic" });
270
264
  const res = await handler(ctx);
271
265
  const body = (await res.json()) as {
272
266
  items: Array<{ id: string }>;
@@ -276,18 +270,16 @@ describe("Memory Item Routes", () => {
276
270
  expect(body.items[0].id).toBe("i1");
277
271
  });
278
272
 
279
- test("filters by search on subject and statement", async () => {
273
+ test("filters by search on content", async () => {
280
274
  insertItem({
281
275
  id: "i1",
282
- kind: "preference",
283
- subject: "dark mode",
284
- statement: "User prefers dark mode",
276
+ type: "semantic",
277
+ content: "dark mode\nUser prefers dark mode",
285
278
  });
286
279
  insertItem({
287
280
  id: "i2",
288
- kind: "identity",
289
- subject: "name",
290
- statement: "User name is Alice",
281
+ type: "episodic",
282
+ content: "name\nUser name is Alice",
291
283
  });
292
284
 
293
285
  const ctx = makeCtx({ search: "dark" });
@@ -303,24 +295,21 @@ describe("Memory Item Routes", () => {
303
295
  test("supports pagination with limit and offset", async () => {
304
296
  insertItem({
305
297
  id: "i1",
306
- kind: "preference",
307
- subject: "s1",
308
- statement: "st1",
309
- lastSeenAt: 1000,
298
+ type: "semantic",
299
+ content: "s1\nst1",
300
+ lastAccessed: 1000,
310
301
  });
311
302
  insertItem({
312
303
  id: "i2",
313
- kind: "preference",
314
- subject: "s2",
315
- statement: "st2",
316
- lastSeenAt: 2000,
304
+ type: "semantic",
305
+ content: "s2\nst2",
306
+ lastAccessed: 2000,
317
307
  });
318
308
  insertItem({
319
309
  id: "i3",
320
- kind: "preference",
321
- subject: "s3",
322
- statement: "st3",
323
- lastSeenAt: 3000,
310
+ type: "semantic",
311
+ content: "s3\nst3",
312
+ lastAccessed: 3000,
324
313
  });
325
314
 
326
315
  const ctx = makeCtx({ limit: "1", offset: "1" });
@@ -338,17 +327,15 @@ describe("Memory Item Routes", () => {
338
327
  test("supports sort by firstSeenAt ascending", async () => {
339
328
  insertItem({
340
329
  id: "i1",
341
- kind: "preference",
342
- subject: "s1",
343
- statement: "st1",
344
- firstSeenAt: 3000,
330
+ type: "semantic",
331
+ content: "s1\nst1",
332
+ created: 3000,
345
333
  });
346
334
  insertItem({
347
335
  id: "i2",
348
- kind: "preference",
349
- subject: "s2",
350
- statement: "st2",
351
- firstSeenAt: 1000,
336
+ type: "semantic",
337
+ content: "s2\nst2",
338
+ created: 1000,
352
339
  });
353
340
 
354
341
  const ctx = makeCtx({ sort: "firstSeenAt", order: "asc" });
@@ -360,32 +347,21 @@ describe("Memory Item Routes", () => {
360
347
  expect(body.items[1].id).toBe("i1");
361
348
  });
362
349
 
363
- test("supports sort by accessCount descending", async () => {
350
+ test("supports sort by importance descending", async () => {
364
351
  insertItem({
365
352
  id: "i1",
366
- kind: "preference",
367
- subject: "s1",
368
- statement: "st1",
353
+ type: "semantic",
354
+ content: "s1\nst1",
355
+ significance: 0.3,
369
356
  });
370
357
  insertItem({
371
358
  id: "i2",
372
- kind: "preference",
373
- subject: "s2",
374
- statement: "st2",
375
- });
376
-
377
- getDb()
378
- .update(memoryItems)
379
- .set({ accessCount: 2 })
380
- .where(eq(memoryItems.id, "i1"))
381
- .run();
382
- getDb()
383
- .update(memoryItems)
384
- .set({ accessCount: 7 })
385
- .where(eq(memoryItems.id, "i2"))
386
- .run();
387
-
388
- const ctx = makeCtx({ sort: "accessCount", order: "desc" });
359
+ type: "semantic",
360
+ content: "s2\nst2",
361
+ significance: 0.9,
362
+ });
363
+
364
+ const ctx = makeCtx({ sort: "importance", order: "desc" });
389
365
  const res = await handler(ctx);
390
366
  const body = (await res.json()) as {
391
367
  items: Array<{ id: string }>;
@@ -411,15 +387,13 @@ describe("Memory Item Routes", () => {
411
387
  test("uses semantic search when embedding backend is available", async () => {
412
388
  insertItem({
413
389
  id: "i1",
414
- kind: "preference",
415
- subject: "dark mode",
416
- statement: "User prefers dark mode",
390
+ type: "semantic",
391
+ content: "dark mode\nUser prefers dark mode",
417
392
  });
418
393
  insertItem({
419
394
  id: "i2",
420
- kind: "identity",
421
- subject: "name",
422
- statement: "User name is Alice",
395
+ type: "episodic",
396
+ content: "name\nUser name is Alice",
423
397
  });
424
398
 
425
399
  // Enable semantic search
@@ -433,12 +407,12 @@ describe("Memory Item Routes", () => {
433
407
  {
434
408
  id: "pt-2",
435
409
  score: 0.95,
436
- payload: { target_type: "item", target_id: "i2" },
410
+ payload: { target_type: "graph_node", target_id: "i2" },
437
411
  },
438
412
  {
439
413
  id: "pt-1",
440
414
  score: 0.7,
441
- payload: { target_type: "item", target_id: "i1" },
415
+ payload: { target_type: "graph_node", target_id: "i1" },
442
416
  },
443
417
  ];
444
418
 
@@ -463,15 +437,13 @@ describe("Memory Item Routes", () => {
463
437
  test("falls back to SQL LIKE when backend is unavailable", async () => {
464
438
  insertItem({
465
439
  id: "i1",
466
- kind: "preference",
467
- subject: "dark mode",
468
- statement: "User prefers dark mode",
440
+ type: "semantic",
441
+ content: "dark mode\nUser prefers dark mode",
469
442
  });
470
443
  insertItem({
471
444
  id: "i2",
472
- kind: "identity",
473
- subject: "name",
474
- statement: "User name is Alice",
445
+ type: "episodic",
446
+ content: "name\nUser name is Alice",
475
447
  });
476
448
 
477
449
  // Backend unavailable
@@ -493,21 +465,18 @@ describe("Memory Item Routes", () => {
493
465
  test("semantic search respects pagination", async () => {
494
466
  insertItem({
495
467
  id: "i1",
496
- kind: "preference",
497
- subject: "s1",
498
- statement: "first item",
468
+ type: "semantic",
469
+ content: "s1\nfirst item",
499
470
  });
500
471
  insertItem({
501
472
  id: "i2",
502
- kind: "preference",
503
- subject: "s2",
504
- statement: "second item",
473
+ type: "semantic",
474
+ content: "s2\nsecond item",
505
475
  });
506
476
  insertItem({
507
477
  id: "i3",
508
- kind: "preference",
509
- subject: "s3",
510
- statement: "third item",
478
+ type: "semantic",
479
+ content: "s3\nthird item",
511
480
  });
512
481
 
513
482
  mockBackendStatus = {
@@ -519,17 +488,17 @@ describe("Memory Item Routes", () => {
519
488
  {
520
489
  id: "pt-1",
521
490
  score: 0.9,
522
- payload: { target_type: "item", target_id: "i1" },
491
+ payload: { target_type: "graph_node", target_id: "i1" },
523
492
  },
524
493
  {
525
494
  id: "pt-2",
526
495
  score: 0.8,
527
- payload: { target_type: "item", target_id: "i2" },
496
+ payload: { target_type: "graph_node", target_id: "i2" },
528
497
  },
529
498
  {
530
499
  id: "pt-3",
531
500
  score: 0.7,
532
- payload: { target_type: "item", target_id: "i3" },
501
+ payload: { target_type: "graph_node", target_id: "i3" },
533
502
  },
534
503
  ];
535
504
 
@@ -553,9 +522,8 @@ describe("Memory Item Routes", () => {
553
522
  test("falls back to SQL when semantic returns empty results", async () => {
554
523
  insertItem({
555
524
  id: "i1",
556
- kind: "preference",
557
- subject: "dark mode",
558
- statement: "User prefers dark mode",
525
+ type: "semantic",
526
+ content: "dark mode\nUser prefers dark mode",
559
527
  });
560
528
 
561
529
  mockBackendStatus = {
@@ -593,9 +561,8 @@ describe("Memory Item Routes", () => {
593
561
  test("returns item by ID", async () => {
594
562
  insertItem({
595
563
  id: "i1",
596
- kind: "preference",
597
- subject: "dark mode",
598
- statement: "Prefers dark mode",
564
+ type: "semantic",
565
+ content: "dark mode\nPrefers dark mode",
599
566
  });
600
567
 
601
568
  const ctx = makeCtx({}, { id: "i1" });
@@ -614,33 +581,20 @@ describe("Memory Item Routes", () => {
614
581
  expect(res.status).toBe(404);
615
582
  });
616
583
 
617
- test("includes supersedesSubject when supersedes is set", async () => {
618
- insertItem({
619
- id: "old",
620
- kind: "preference",
621
- subject: "old pref",
622
- statement: "old",
623
- });
584
+ test("returns null for legacy supersedes/supersededBy fields", async () => {
624
585
  insertItem({
625
- id: "new",
626
- kind: "preference",
627
- subject: "new pref",
628
- statement: "new",
586
+ id: "i1",
587
+ type: "semantic",
588
+ content: "some content\nsome statement",
629
589
  });
630
590
 
631
- // Set supersedes relationship manually
632
- getDb()
633
- .update(memoryItems)
634
- .set({ supersedes: "old" })
635
- .where(eq(memoryItems.id, "new"))
636
- .run();
637
-
638
- const ctx = makeCtx({}, { id: "new" });
591
+ const ctx = makeCtx({}, { id: "i1" });
639
592
  const res = await handler(ctx);
640
593
  const body = (await res.json()) as {
641
- item: { supersedesSubject?: string };
594
+ item: { supersedes: unknown; supersededBy: unknown };
642
595
  };
643
- expect(body.item.supersedesSubject).toBe("old pref");
596
+ expect(body.item.supersedes).toBeNull();
597
+ expect(body.item.supersededBy).toBeNull();
644
598
  });
645
599
  });
646
600
 
@@ -653,7 +607,7 @@ describe("Memory Item Routes", () => {
653
607
 
654
608
  test("creates a new memory item", async () => {
655
609
  const ctx = makeJsonCtx("memory-items", "POST", {
656
- kind: "preference",
610
+ kind: "semantic",
657
611
  subject: "dark mode",
658
612
  statement: "User prefers dark mode",
659
613
  });
@@ -662,14 +616,14 @@ describe("Memory Item Routes", () => {
662
616
  const body = (await res.json()) as {
663
617
  item: { id: string; kind: string; subject: string; statement: string };
664
618
  };
665
- expect(body.item.kind).toBe("preference");
619
+ expect(body.item.kind).toBe("semantic");
666
620
  expect(body.item.subject).toBe("dark mode");
667
621
  expect(body.item.statement).toBe("User prefers dark mode");
668
622
  });
669
623
 
670
624
  test("uses custom importance when provided", async () => {
671
625
  const ctx = makeJsonCtx("memory-items", "POST", {
672
- kind: "preference",
626
+ kind: "semantic",
673
627
  subject: "importance test",
674
628
  statement: "Testing custom importance",
675
629
  importance: 0.5,
@@ -682,9 +636,9 @@ describe("Memory Item Routes", () => {
682
636
  expect(body.item.importance).toBe(0.5);
683
637
  });
684
638
 
685
- test("rejects duplicate fingerprint", async () => {
639
+ test("rejects duplicate content", async () => {
686
640
  const payload = {
687
- kind: "preference",
641
+ kind: "semantic",
688
642
  subject: "dark mode",
689
643
  statement: "User prefers dark mode",
690
644
  };
@@ -707,18 +661,24 @@ describe("Memory Item Routes", () => {
707
661
  expect(res.status).toBe(400);
708
662
  });
709
663
 
710
- test("rejects missing subject", async () => {
664
+ test("accepts missing subject (optional)", async () => {
711
665
  const ctx = makeJsonCtx("memory-items", "POST", {
712
- kind: "preference",
713
- statement: "test",
666
+ kind: "semantic",
667
+ statement: "test content without subject",
714
668
  });
715
669
  const res = await handler(ctx);
716
- expect(res.status).toBe(400);
670
+ expect(res.status).toBe(201);
671
+ const body = (await res.json()) as {
672
+ item: { subject: string; statement: string };
673
+ };
674
+ // When no subject, content has no newline, so subject and statement are the same
675
+ expect(body.item.subject).toBe("test content without subject");
676
+ expect(body.item.statement).toBe("test content without subject");
717
677
  });
718
678
 
719
679
  test("rejects missing statement", async () => {
720
680
  const ctx = makeJsonCtx("memory-items", "POST", {
721
- kind: "preference",
681
+ kind: "semantic",
722
682
  subject: "test",
723
683
  });
724
684
  const res = await handler(ctx);
@@ -729,7 +689,7 @@ describe("Memory Item Routes", () => {
729
689
  const longSubject = "a".repeat(200);
730
690
  const longStatement = "b".repeat(1000);
731
691
  const ctx = makeJsonCtx("memory-items", "POST", {
732
- kind: "preference",
692
+ kind: "semantic",
733
693
  subject: longSubject,
734
694
  statement: longStatement,
735
695
  });
@@ -744,7 +704,7 @@ describe("Memory Item Routes", () => {
744
704
 
745
705
  test("enqueues embed job on create", async () => {
746
706
  const ctx = makeJsonCtx("memory-items", "POST", {
747
- kind: "preference",
707
+ kind: "semantic",
748
708
  subject: "embed test",
749
709
  statement: "Should enqueue embed job",
750
710
  });
@@ -754,7 +714,7 @@ describe("Memory Item Routes", () => {
754
714
  const db = getDb();
755
715
  const jobs = db.select().from(memoryJobs).all();
756
716
  const embedJobs = jobs.filter(
757
- (j) => j.type === "embed_item" && j.status === "pending",
717
+ (j) => j.type === "embed_graph_node" && j.status === "pending",
758
718
  );
759
719
  expect(embedJobs.length).toBeGreaterThanOrEqual(1);
760
720
  });
@@ -770,9 +730,8 @@ describe("Memory Item Routes", () => {
770
730
  test("updates subject and statement", async () => {
771
731
  insertItem({
772
732
  id: "i1",
773
- kind: "preference",
774
- subject: "old subject",
775
- statement: "old statement",
733
+ type: "semantic",
734
+ content: "old subject\nold statement",
776
735
  });
777
736
 
778
737
  const ctx = makeJsonCtx(
@@ -801,17 +760,16 @@ describe("Memory Item Routes", () => {
801
760
  expect(res.status).toBe(404);
802
761
  });
803
762
 
804
- test("detects fingerprint collision on update", async () => {
763
+ test("detects content collision on update", async () => {
805
764
  insertItem({
806
765
  id: "i1",
807
- kind: "preference",
808
- subject: "first",
809
- statement: "first statement",
766
+ type: "semantic",
767
+ content: "first\nfirst statement",
810
768
  });
811
- // Insert a second item using the create handler to get a real fingerprint
769
+ // Insert a second item using the create handler to get a real node
812
770
  const createHandler = getHandler("memory-items", "POST");
813
771
  const createCtx = makeJsonCtx("memory-items", "POST", {
814
- kind: "preference",
772
+ kind: "semantic",
815
773
  subject: "second",
816
774
  statement: "second statement",
817
775
  });
@@ -832,29 +790,27 @@ describe("Memory Item Routes", () => {
832
790
  test("allows updating kind", async () => {
833
791
  insertItem({
834
792
  id: "i1",
835
- kind: "preference",
836
- subject: "test",
837
- statement: "test",
793
+ type: "semantic",
794
+ content: "test\ntest",
838
795
  });
839
796
 
840
797
  const ctx = makeJsonCtx(
841
798
  "memory-items/i1",
842
799
  "PATCH",
843
- { kind: "identity" },
800
+ { kind: "episodic" },
844
801
  { id: "i1" },
845
802
  );
846
803
  const res = await handler(ctx);
847
804
  expect(res.status).toBe(200);
848
805
  const body = (await res.json()) as { item: { kind: string } };
849
- expect(body.item.kind).toBe("identity");
806
+ expect(body.item.kind).toBe("episodic");
850
807
  });
851
808
 
852
809
  test("rejects invalid kind on update", async () => {
853
810
  insertItem({
854
811
  id: "i1",
855
- kind: "preference",
856
- subject: "test",
857
- statement: "test",
812
+ type: "semantic",
813
+ content: "test\ntest",
858
814
  });
859
815
 
860
816
  const ctx = makeJsonCtx(
@@ -870,9 +826,8 @@ describe("Memory Item Routes", () => {
870
826
  test("enqueues embed job when statement changes", async () => {
871
827
  insertItem({
872
828
  id: "i1",
873
- kind: "preference",
874
- subject: "test",
875
- statement: "old statement",
829
+ type: "semantic",
830
+ content: "test\nold statement",
876
831
  });
877
832
 
878
833
  // Clear jobs first
@@ -889,7 +844,7 @@ describe("Memory Item Routes", () => {
889
844
  const db = getDb();
890
845
  const jobs = db.select().from(memoryJobs).all();
891
846
  const embedJobs = jobs.filter(
892
- (j) => j.type === "embed_item" && j.status === "pending",
847
+ (j) => j.type === "embed_graph_node" && j.status === "pending",
893
848
  );
894
849
  expect(embedJobs.length).toBe(1);
895
850
  });
@@ -905,23 +860,22 @@ describe("Memory Item Routes", () => {
905
860
  test("deletes item and returns 204", async () => {
906
861
  insertItem({
907
862
  id: "i1",
908
- kind: "preference",
909
- subject: "test",
910
- statement: "test",
863
+ type: "semantic",
864
+ content: "test\ntest",
911
865
  });
912
866
 
913
867
  const ctx = makeJsonCtx("memory-items/i1", "DELETE", null, { id: "i1" });
914
868
  const res = await handler(ctx);
915
869
  expect(res.status).toBe(204);
916
870
 
917
- // Verify the item is gone
871
+ // Verify the node is gone
918
872
  const db = getDb();
919
- const item = db
873
+ const node = db
920
874
  .select()
921
- .from(memoryItems)
922
- .where(eq(memoryItems.id, "i1"))
875
+ .from(memoryGraphNodes)
876
+ .where(eq(memoryGraphNodes.id, "i1"))
923
877
  .get();
924
- expect(item).toBeUndefined();
878
+ expect(node).toBeUndefined();
925
879
  });
926
880
 
927
881
  test("returns 404 for non-existent item", async () => {
@@ -932,46 +886,27 @@ describe("Memory Item Routes", () => {
932
886
  expect(res.status).toBe(404);
933
887
  });
934
888
 
935
- test("also deletes associated embeddings", async () => {
889
+ test("enqueues delete_qdrant_vectors job on delete", async () => {
936
890
  insertItem({
937
891
  id: "i1",
938
- kind: "preference",
939
- subject: "test",
940
- statement: "test",
892
+ type: "semantic",
893
+ content: "test\ntest",
941
894
  });
942
895
 
943
- // Insert an embedding for this item
944
- const db = getDb();
945
- db.insert(memoryEmbeddings)
946
- .values({
947
- id: "emb-1",
948
- targetType: "item",
949
- targetId: "i1",
950
- provider: "test",
951
- model: "test-model",
952
- dimensions: 384,
953
- vectorJson: "[]",
954
- createdAt: Date.now(),
955
- updatedAt: Date.now(),
956
- })
957
- .run();
958
-
959
896
  const ctx = makeJsonCtx("memory-items/i1", "DELETE", null, { id: "i1" });
960
897
  const res = await handler(ctx);
961
898
  expect(res.status).toBe(204);
962
899
 
963
- // Verify embedding is also gone
964
- const emb = db
965
- .select()
966
- .from(memoryEmbeddings)
967
- .where(
968
- and(
969
- eq(memoryEmbeddings.targetType, "item"),
970
- eq(memoryEmbeddings.targetId, "i1"),
971
- ),
972
- )
973
- .get();
974
- expect(emb).toBeUndefined();
900
+ // Verify a delete_qdrant_vectors job was enqueued with graph_node targetType
901
+ const db = getDb();
902
+ const jobs = db.select().from(memoryJobs).all();
903
+ const deleteJobs = jobs.filter(
904
+ (j) => j.type === "delete_qdrant_vectors" && j.status === "pending",
905
+ );
906
+ expect(deleteJobs.length).toBe(1);
907
+ const payload = JSON.parse(deleteJobs[0].payload);
908
+ expect(payload.targetType).toBe("graph_node");
909
+ expect(payload.targetId).toBe("i1");
975
910
  });
976
911
  });
977
912
  });