memorix 1.2.7 → 1.2.8

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.
@@ -1,6 +1,7 @@
1
- import type {
2
- MaintenanceJobHandler,
3
- MaintenanceJobRunResult,
1
+ import {
2
+ MaintenanceJobStore,
3
+ type MaintenanceJobHandler,
4
+ type MaintenanceJobRunResult,
4
5
  } from './maintenance-jobs.js';
5
6
  import { runMaintenanceInChildProcess } from './isolated-maintenance.js';
6
7
  import {
@@ -18,6 +19,8 @@ const DEFAULT_CODEGRAPH_MAX_FILES = 5_000;
18
19
  const DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
19
20
  const DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
20
21
  const VECTOR_RETRY_DELAY_MS = 5_000;
22
+ const VECTOR_FAILURE_BASE_RETRY_DELAY_MS = 60_000;
23
+ const VECTOR_FAILURE_MAX_RETRY_DELAY_MS = 15 * 60_000;
21
24
  const DEDUP_PER_PAIR_TIMEOUT_MS = 5_000;
22
25
 
23
26
  function vectorBatchSize(payload: Record<string, unknown>): number {
@@ -26,6 +29,33 @@ function vectorBatchSize(payload: Record<string, unknown>): number {
26
29
  return Math.min(100, Math.max(1, Math.floor(value)));
27
30
  }
28
31
 
32
+ function vectorBackfillFailureStreak(payload: Record<string, unknown>): number {
33
+ const value = payload.vectorBackfillFailureStreak;
34
+ return typeof value === 'number' && Number.isFinite(value)
35
+ ? Math.min(16, Math.max(0, Math.floor(value)))
36
+ : 0;
37
+ }
38
+
39
+ function vectorBackfillRetryDelayMs(streak: number): number {
40
+ return Math.min(
41
+ VECTOR_FAILURE_MAX_RETRY_DELAY_MS,
42
+ VECTOR_FAILURE_BASE_RETRY_DELAY_MS * (2 ** Math.max(0, streak - 1)),
43
+ );
44
+ }
45
+
46
+ function withoutVectorBackfillFailureStreak(payload: Record<string, unknown>): Record<string, unknown> {
47
+ const { vectorBackfillFailureStreak: _streak, ...rest } = payload;
48
+ return rest;
49
+ }
50
+
51
+ function resolveSupersededVectorFailures(projectDir: string, projectId: string): void {
52
+ try {
53
+ new MaintenanceJobStore(projectDir).resolveFailedVectorBackfills(projectId);
54
+ } catch {
55
+ // Health cleanup must never block the live memory path.
56
+ }
57
+ }
58
+
29
59
  function retentionBatchSize(payload: Record<string, unknown>): number {
30
60
  const value = payload.limit;
31
61
  if (typeof value !== 'number' || !Number.isFinite(value)) return DEFAULT_RETENTION_BATCH_SIZE;
@@ -498,23 +528,50 @@ export function createProjectMaintenanceHandler(
498
528
 
499
529
  const { backfillVectorEmbeddings, getVectorStatus } = await import('../memory/observations.js');
500
530
  const before = getVectorStatus(projectId);
501
- if (before.missing === 0) return { action: 'complete' };
531
+ if (before.missing === 0) {
532
+ resolveSupersededVectorFailures(projectDir, projectId);
533
+ return { action: 'complete' };
534
+ }
502
535
 
503
536
  const result = await backfillVectorEmbeddings({
504
537
  projectId,
505
538
  limit: vectorBatchSize(job.payload),
506
539
  });
507
540
  const after = getVectorStatus(projectId);
508
- if (after.missing === 0) return { action: 'complete' };
541
+ if (after.missing === 0) {
542
+ resolveSupersededVectorFailures(projectDir, projectId);
543
+ return { action: 'complete' };
544
+ }
509
545
 
510
546
  if (result.failed > 0 && result.succeeded === 0) {
511
- throw new Error(`vector backfill made no progress (${result.failed}/${result.attempted} failed)`);
547
+ const streak = vectorBackfillFailureStreak(job.payload) + 1;
548
+ return {
549
+ action: 'reschedule',
550
+ status: 'retry',
551
+ delayMs: vectorBackfillRetryDelayMs(streak),
552
+ // This is a recoverable provider/index state, not a terminal job error.
553
+ // Keeping one retry row prevents new MCP processes from creating a storm.
554
+ resetAttempts: true,
555
+ payload: {
556
+ ...job.payload,
557
+ vectorBackfillFailureStreak: streak,
558
+ },
559
+ lastError: result.lastError ?? `vector backfill made no progress (${result.failed}/${result.attempted} failed)`,
560
+ };
512
561
  }
513
562
 
563
+ const priorFailureStreak = vectorBackfillFailureStreak(job.payload);
564
+ const payload = withoutVectorBackfillFailureStreak(job.payload);
514
565
  return {
515
566
  action: 'reschedule',
516
567
  delayMs: result.attempted === 0 ? VECTOR_RETRY_DELAY_MS : 0,
517
568
  resetAttempts: result.succeeded > 0,
569
+ ...(result.succeeded > 0 ? {
570
+ ...(priorFailureStreak > 0 ? { payload } : {}),
571
+ ...(result.failed > 0 && result.lastError
572
+ ? { lastError: result.lastError }
573
+ : priorFailureStreak > 0 ? { clearLastError: true } : {}),
574
+ } : {}),
518
575
  };
519
576
  };
520
577
  }