feat(memory): add gated agent recall and lifecycle controls
This commit is contained in:
@@ -13,6 +13,7 @@ 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';
|
||||
|
||||
const DEFAULT_PGVECTOR_URL_ENV = 'MEMORY_PGVECTOR_DATABASE_URL';
|
||||
|
||||
@@ -416,6 +417,23 @@ export async function createMemoryV2Runtime({
|
||||
logger,
|
||||
personalShadowPipeline,
|
||||
});
|
||||
const lifecycle = createMemoryV2LifecycleService({ pool: mysqlPool, env, logger });
|
||||
let lifecycleTimer = null;
|
||||
const lifecycleWorkerEnabled = readFlag(env, 'MEMORY_LIFECYCLE_WORKER_ENABLED', false);
|
||||
if (lifecycleWorkerEnabled && mysqlPool?.query) {
|
||||
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}`);
|
||||
});
|
||||
lifecycleTimer = setInterval(runLifecycle, intervalMs);
|
||||
lifecycleTimer.unref?.();
|
||||
}
|
||||
const originalGetStatus = memory.getStatus.bind(memory);
|
||||
memory.getStatus = () => ({
|
||||
...originalGetStatus(),
|
||||
lifecycle: lifecycle.getStatus(),
|
||||
});
|
||||
memory.lifecycle = lifecycle;
|
||||
|
||||
const pgBackfillEnabled = readFlag(env, 'MEMORY_PGVECTOR_BACKFILL_ENABLED', pgvectorEnabled);
|
||||
let pgBackfillBusy = false;
|
||||
@@ -467,6 +485,7 @@ export async function createMemoryV2Runtime({
|
||||
}
|
||||
|
||||
memory.close = async () => {
|
||||
if (lifecycleTimer) clearInterval(lifecycleTimer);
|
||||
const results = await Promise.allSettled(closers.map((close) => close()));
|
||||
for (const result of results) {
|
||||
if (result.status === 'rejected') {
|
||||
@@ -608,6 +627,28 @@ export async function createManagedMemoryV2Runtime({
|
||||
return runtime.observePersonalMemory(input);
|
||||
},
|
||||
|
||||
async listMemories(input = {}) {
|
||||
const runtime = await ensureRuntime();
|
||||
return runtime.lifecycle?.listMemories?.(input) ?? [];
|
||||
},
|
||||
|
||||
async forgetMemory(input = {}) {
|
||||
const runtime = await ensureRuntime();
|
||||
return runtime.lifecycle?.forgetMemory?.(input) ?? { ok: false, skipped: true, reason: 'unavailable' };
|
||||
},
|
||||
|
||||
async runLifecycle(input = {}) {
|
||||
const runtime = await ensureRuntime();
|
||||
const lifecycle = runtime.lifecycle;
|
||||
if (!lifecycle) return { ok: false, reason: 'unavailable' };
|
||||
return {
|
||||
expire: await lifecycle.expire(input),
|
||||
compact: await lifecycle.compact(input),
|
||||
promote: await lifecycle.promote(input),
|
||||
reflect: await lifecycle.reflect(input),
|
||||
};
|
||||
},
|
||||
|
||||
async close() {
|
||||
const runtime = activeRuntime;
|
||||
activeRuntime = null;
|
||||
|
||||
Reference in New Issue
Block a user