@vellumai/assistant 0.5.3 → 0.5.4

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 (57) hide show
  1. package/docs/architecture/memory.md +105 -0
  2. package/package.json +1 -1
  3. package/src/__tests__/archive-recall.test.ts +560 -0
  4. package/src/__tests__/conversation-clear-safety.test.ts +259 -0
  5. package/src/__tests__/conversation-switch-memory-reduction.test.ts +474 -0
  6. package/src/__tests__/db-schedule-syntax-migration.test.ts +3 -0
  7. package/src/__tests__/memory-reducer-job.test.ts +538 -0
  8. package/src/__tests__/memory-reducer-scheduling.test.ts +473 -0
  9. package/src/__tests__/memory-reducer-types.test.ts +12 -4
  10. package/src/__tests__/memory-reducer.test.ts +7 -1
  11. package/src/__tests__/memory-regressions.test.ts +24 -4
  12. package/src/__tests__/memory-simplified-config.test.ts +4 -4
  13. package/src/__tests__/simplified-memory-e2e.test.ts +666 -0
  14. package/src/__tests__/simplified-memory-runtime.test.ts +616 -0
  15. package/src/cli/commands/conversations.ts +18 -0
  16. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  17. package/src/config/loader.ts +0 -1
  18. package/src/config/schemas/memory-simplified.ts +1 -1
  19. package/src/daemon/conversation-memory.ts +117 -0
  20. package/src/daemon/conversation-runtime-assembly.ts +1 -0
  21. package/src/daemon/handlers/conversations.ts +11 -0
  22. package/src/daemon/lifecycle.ts +44 -1
  23. package/src/memory/archive-recall.ts +516 -0
  24. package/src/memory/brief-time.ts +5 -4
  25. package/src/memory/conversation-crud.ts +210 -0
  26. package/src/memory/conversation-key-store.ts +33 -4
  27. package/src/memory/db-init.ts +4 -0
  28. package/src/memory/job-handlers/backfill-simplified-memory.ts +462 -0
  29. package/src/memory/job-handlers/conversation-starters.ts +9 -3
  30. package/src/memory/job-handlers/reduce-conversation-memory.ts +229 -0
  31. package/src/memory/jobs-store.ts +2 -0
  32. package/src/memory/jobs-worker.ts +8 -0
  33. package/src/memory/migrations/036-normalize-phone-identities.ts +49 -14
  34. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +9 -1
  35. package/src/memory/migrations/141-rename-verification-table.ts +8 -0
  36. package/src/memory/migrations/142-rename-verification-session-id-column.ts +7 -2
  37. package/src/memory/migrations/174-rename-thread-starters-table.ts +8 -0
  38. package/src/memory/migrations/188-schedule-quiet-flag.ts +13 -0
  39. package/src/memory/migrations/index.ts +1 -0
  40. package/src/memory/reducer-scheduler.ts +242 -0
  41. package/src/memory/reducer-types.ts +9 -2
  42. package/src/memory/reducer.ts +25 -11
  43. package/src/memory/schema/infrastructure.ts +1 -0
  44. package/src/runtime/auth/route-policy.ts +10 -1
  45. package/src/runtime/routes/conversation-management-routes.ts +88 -2
  46. package/src/runtime/routes/guardian-bootstrap-routes.ts +19 -7
  47. package/src/runtime/routes/secret-routes.ts +1 -0
  48. package/src/schedule/schedule-store.ts +7 -0
  49. package/src/schedule/scheduler.ts +6 -2
  50. package/src/telemetry/usage-telemetry-reporter.ts +1 -1
  51. package/src/tools/filesystem/edit.ts +6 -1
  52. package/src/tools/filesystem/read.ts +6 -1
  53. package/src/tools/filesystem/write.ts +6 -1
  54. package/src/tools/memory/handlers.ts +129 -1
  55. package/src/tools/schedule/create.ts +3 -0
  56. package/src/tools/schedule/list.ts +5 -1
  57. package/src/tools/schedule/update.ts +6 -0
@@ -28,8 +28,13 @@ class FileWriteTool implements Tool {
28
28
  type: "string",
29
29
  description: "The content to write to the file",
30
30
  },
31
+ activity: {
32
+ type: "string",
33
+ description:
34
+ "Brief non-technical explanation of what you are doing and why, shown to the user as a status update.",
35
+ },
31
36
  },
32
- required: ["path", "content"],
37
+ required: ["path", "content", "activity"],
33
38
  },
34
39
  };
35
40
  }
@@ -2,6 +2,8 @@ import { and, eq, ne } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import type { AssistantConfig } from "../../config/types.js";
5
+ import { buildArchiveRecall } from "../../memory/archive-recall.js";
6
+ import { insertObservation } from "../../memory/archive-store.js";
5
7
  import { getDb } from "../../memory/db.js";
6
8
  import { computeMemoryFingerprint } from "../../memory/fingerprint.js";
7
9
  import { enqueueMemoryJob } from "../../memory/jobs-store.js";
@@ -18,7 +20,7 @@ const log = getLogger("memory-tools");
18
20
 
19
21
  export async function handleMemorySave(
20
22
  args: Record<string, unknown>,
21
- _config: AssistantConfig,
23
+ config: AssistantConfig,
22
24
  conversationId: string,
23
25
  messageId: string | undefined,
24
26
  scopeId: string = "default",
@@ -63,6 +65,19 @@ export async function handleMemorySave(
63
65
  ? truncate(args.subject.trim(), 80, "")
64
66
  : inferSubjectFromStatement(statement.trim());
65
67
 
68
+ // When simplified memory is enabled, save directly to the simplified
69
+ // observation/chunk tables instead of the legacy memory_items table.
70
+ if (config.memory.simplified.enabled) {
71
+ return handleSimplifiedMemorySave(
72
+ kind,
73
+ subject,
74
+ statement.trim(),
75
+ conversationId,
76
+ messageId,
77
+ scopeId,
78
+ );
79
+ }
80
+
66
81
  try {
67
82
  const db = getDb();
68
83
  const id = uuid();
@@ -275,6 +290,12 @@ export async function handleMemoryRecall(
275
290
  ? args.scope.trim()
276
291
  : "default";
277
292
 
293
+ // When simplified memory is enabled, use the archive recall path
294
+ // instead of the legacy hybrid retriever.
295
+ if (config.memory.simplified.enabled) {
296
+ return handleSimplifiedMemoryRecall(query.trim(), scopeId ?? "default");
297
+ }
298
+
278
299
  // Scope policy: "conversation" means strict (only that scope),
279
300
  // anything else allows fallback to the default scope.
280
301
  const scopePolicyOverride: ScopePolicyOverride | undefined = scopeId
@@ -411,6 +432,113 @@ export async function handleMemoryDelete(
411
432
  }
412
433
  }
413
434
 
435
+ // ── Simplified memory helpers ────────────────────────────────────────
436
+
437
+ /**
438
+ * Save a memory item as an observation + chunk in the simplified system.
439
+ * This is used when simplified memory is enabled instead of writing to
440
+ * the legacy memory_items table.
441
+ */
442
+ function handleSimplifiedMemorySave(
443
+ kind: string,
444
+ subject: string,
445
+ statement: string,
446
+ conversationId: string,
447
+ messageId: string | undefined,
448
+ scopeId: string,
449
+ ): ToolExecutionResult {
450
+ try {
451
+ const trimmedStatement = truncate(statement, 500, "");
452
+ const content = `[${kind}] ${subject}: ${trimmedStatement}`;
453
+
454
+ const result = insertObservation({
455
+ conversationId,
456
+ messageId: messageId ?? null,
457
+ role: "user",
458
+ content,
459
+ scopeId,
460
+ modality: "text",
461
+ source: "tool:memory_save",
462
+ });
463
+
464
+ log.debug(
465
+ {
466
+ observationId: result.observationId,
467
+ chunkId: result.chunkId,
468
+ kind,
469
+ subject,
470
+ conversationId,
471
+ messageId,
472
+ },
473
+ "Memory saved via simplified system",
474
+ );
475
+
476
+ return {
477
+ content: `Saved to memory (ID: ${result.observationId}).\nKind: ${kind}\nSubject: ${subject}\nStatement: ${trimmedStatement}`,
478
+ isError: false,
479
+ };
480
+ } catch (err) {
481
+ const msg = err instanceof Error ? err.message : String(err);
482
+ log.error({ err }, "simplified memory_save failed");
483
+ return { content: `Error: Failed to save memory: ${msg}`, isError: true };
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Recall memories using the simplified archive recall path instead of
489
+ * the legacy hybrid retriever.
490
+ */
491
+ function handleSimplifiedMemoryRecall(
492
+ query: string,
493
+ scopeId: string,
494
+ ): ToolExecutionResult {
495
+ try {
496
+ const recallResult = buildArchiveRecall(scopeId, query);
497
+
498
+ if (recallResult.bullets.length === 0) {
499
+ return {
500
+ content: JSON.stringify({
501
+ text: "No matching memories found.",
502
+ resultCount: 0,
503
+ degraded: false,
504
+ items: [],
505
+ sources: { semantic: 0, recency: 0 },
506
+ }),
507
+ isError: false,
508
+ };
509
+ }
510
+
511
+ const items = recallResult.bullets.map((b) => ({
512
+ id: b.sourceId,
513
+ type: b.source,
514
+ kind: b.source,
515
+ }));
516
+
517
+ const result = {
518
+ text: recallResult.text,
519
+ resultCount: recallResult.bullets.length,
520
+ degraded: false,
521
+ items,
522
+ sources: {
523
+ semantic: recallResult.prefetchHitCount,
524
+ recency: 0,
525
+ },
526
+ };
527
+
528
+ return {
529
+ content: JSON.stringify(result),
530
+ isError: false,
531
+ };
532
+ } catch (err) {
533
+ const msg = err instanceof Error ? err.message : String(err);
534
+ log.error({ err, query }, "simplified memory_recall failed");
535
+ return {
536
+ content: `Error: Memory recall failed: ${msg}`,
537
+ isError: true,
538
+ };
539
+ }
540
+ }
541
+
414
542
  // ── Helpers ──────────────────────────────────────────────────────────
415
543
 
416
544
  function inferSubjectFromStatement(statement: string): string {
@@ -41,6 +41,7 @@ export async function executeScheduleCreate(
41
41
  const routingHints = input.routing_hints as
42
42
  | Record<string, unknown>
43
43
  | undefined;
44
+ const quiet = (input.quiet as boolean) ?? false;
44
45
 
45
46
  if (!name || typeof name !== "string") {
46
47
  return {
@@ -112,6 +113,7 @@ export async function executeScheduleCreate(
112
113
  mode,
113
114
  routingIntent: routingIntent as RoutingIntent | undefined,
114
115
  routingHints,
116
+ quiet,
115
117
  });
116
118
 
117
119
  const fireDate = formatLocalDate(job.nextRunAt);
@@ -187,6 +189,7 @@ export async function executeScheduleCreate(
187
189
  mode,
188
190
  routingIntent: routingIntent as RoutingIntent | undefined,
189
191
  routingHints,
192
+ quiet,
190
193
  });
191
194
 
192
195
  const scheduleDescription =
@@ -62,7 +62,11 @@ export async function executeScheduleList(
62
62
  );
63
63
  }
64
64
 
65
- lines.push(` Enabled: ${job.enabled}`, ` Message: ${job.message}`);
65
+ lines.push(
66
+ ` Enabled: ${job.enabled}`,
67
+ ` Quiet: ${job.quiet}`,
68
+ ` Message: ${job.message}`,
69
+ );
66
70
 
67
71
  if (!oneShot) {
68
72
  lines.push(` Next run: ${formatLocalDate(job.nextRunAt)}`);
@@ -97,6 +97,11 @@ export async function executeScheduleUpdate(
97
97
  updates.routingHints = input.routing_hints;
98
98
  }
99
99
 
100
+ // Quiet mode
101
+ if (input.quiet !== undefined) {
102
+ updates.quiet = input.quiet;
103
+ }
104
+
100
105
  // Auto-detect syntax when expression changes without explicit syntax
101
106
  if (input.expression !== undefined || input.syntax !== undefined) {
102
107
  const resolved = normalizeScheduleSyntax({
@@ -159,6 +164,7 @@ export async function executeScheduleUpdate(
159
164
  mode?: ScheduleMode;
160
165
  routingIntent?: RoutingIntent;
161
166
  routingHints?: Record<string, unknown>;
167
+ quiet?: boolean;
162
168
  },
163
169
  );
164
170