fix(memory): initialize candidates and enforce lifecycle rollout
Memind CI / Test, build, and release guards (push) Successful in 3m29s

This commit is contained in:
john
2026-07-21 22:55:19 +08:00
parent 0dd9331e5b
commit bfb1f6fea9
8 changed files with 207 additions and 14 deletions
+34 -9
View File
@@ -5,7 +5,10 @@ import { createLegacyMemoryBackend, createMemoryV2, resolveMemoryV2Policy } from
import { createLettaHttpClient, createLettaMemoryBackend } from './memory-v2-letta.mjs';
import { createMemoryV2PluginBackends } from './memory-v2-plugin-backends.mjs';
import { createPersonalMemoryShadowPipeline } from './memory-v2-personal-shadow.mjs';
import { createPersonalMemoryCandidateStore } from './memory-v2-personal-store.mjs';
import {
createPersonalMemoryCandidateStore,
ensurePersonalMemoryCandidateSchema,
} from './memory-v2-personal-store.mjs';
import { createMem0HttpClient, createMem0MemoryBackend } from './memory-v2-mem0.mjs';
import { createNeo4jHttpClient, createNeo4jMemoryBackend } from './memory-v2-neo4j.mjs';
import { createPgvectorMemoryBackend } from './memory-v2-pgvector.mjs';
@@ -13,7 +16,10 @@ import { backfillLegacyMemoriesToPgvector } from './memory-v2-pgvector-backfill.
import { createQdrantHttpClient, createQdrantMemoryBackend } from './memory-v2-qdrant.mjs';
import { createRedisStreamsClient, createRedisStreamsMemoryBackend } from './memory-v2-redis-streams.mjs';
import { createWeaviateHttpClient, createWeaviateMemoryBackend } from './memory-v2-weaviate.mjs';
import { createMemoryV2LifecycleService } from './memory-v2-lifecycle.mjs';
import {
createMemoryV2LifecycleService,
resolveMemoryV2LifecycleWorkerScopes,
} from './memory-v2-lifecycle.mjs';
const DEFAULT_PGVECTOR_URL_ENV = 'MEMORY_PGVECTOR_DATABASE_URL';
@@ -403,9 +409,17 @@ export async function createMemoryV2Runtime({
],
}));
const personalCandidateStore = readFlag(env, 'MEMORY_CANDIDATE_PERSISTENCE_ENABLED', false)
? createPersonalMemoryCandidateStore(mysqlPool)
: null;
let personalCandidateStore = null;
if (readFlag(env, 'MEMORY_CANDIDATE_PERSISTENCE_ENABLED', false) && mysqlPool?.query) {
try {
await ensurePersonalMemoryCandidateSchema(mysqlPool);
personalCandidateStore = createPersonalMemoryCandidateStore(mysqlPool);
} catch (err) {
logger?.warn?.(
`[memory-v2] candidate persistence unavailable: ${err instanceof Error ? err.message : err}`,
);
}
}
const personalShadowPipeline = createPersonalMemoryShadowPipeline({
env,
store: personalCandidateStore,
@@ -420,11 +434,22 @@ export async function createMemoryV2Runtime({
const lifecycle = createMemoryV2LifecycleService({ pool: mysqlPool, env, logger });
let lifecycleTimer = null;
const lifecycleWorkerEnabled = readFlag(env, 'MEMORY_LIFECYCLE_WORKER_ENABLED', false);
if (lifecycleWorkerEnabled && mysqlPool?.query) {
const lifecycleWorkerScopes = resolveMemoryV2LifecycleWorkerScopes(lifecycle.policy);
if (lifecycleWorkerEnabled && mysqlPool?.query && lifecycleWorkerScopes.length > 0) {
const intervalMs = Math.max(60_000, Number(lifecycle.policy.compactIntervalHours ?? 24) * 3_600_000);
const runLifecycle = () => lifecycle.expire().then(() => lifecycle.compact()).then(() => lifecycle.promote()).then(() => lifecycle.reflect()).catch((err) => {
logger?.warn?.(`[memory-v2] lifecycle worker skipped: ${err instanceof Error ? err.message : err}`);
});
const runLifecycle = async () => {
try {
for (const userId of lifecycleWorkerScopes) {
const input = userId == null ? {} : { userId };
await lifecycle.expire(input);
await lifecycle.compact(input);
await lifecycle.promote(input);
await lifecycle.reflect(input);
}
} catch (err) {
logger?.warn?.(`[memory-v2] lifecycle worker skipped: ${err instanceof Error ? err.message : err}`);
}
};
lifecycleTimer = setInterval(runLifecycle, intervalMs);
lifecycleTimer.unref?.();
}