Files
memind/memory-v2-phase-a-ready.mjs
T
john 6f3e53a56a feat(memory-v2): close Phase A with auto-review, product events, and H5 recall UI.
Add candidate auto-review pipeline, shadow audit tooling, admin metrics page,
and user-visible memory recall hints in chat with phase-a readiness checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:14:06 +08:00

97 lines
4.0 KiB
JavaScript

const TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);
function readFlag(env, key, fallback = false) {
const raw = env?.[key];
if (raw == null || raw === '') return fallback;
return TRUE_VALUES.has(String(raw).trim().toLowerCase());
}
function normalizeUserIds(value) {
return String(value ?? '')
.split(/[\s,]+/u)
.map((item) => item.trim())
.filter(Boolean);
}
export function evaluateMemoryV2PhaseAReadiness(env = process.env) {
const issues = [];
const warnings = [];
const agentCanaryUserIds = normalizeUserIds(env.MEMORY_AGENT_CANARY_USER_IDS);
const lifecycleUserIds = normalizeUserIds(env.MEMORY_LIFECYCLE_ROLLOUT_USER_IDS);
const candidateMode = String(env.MEMORY_CANDIDATE_MODE ?? 'off').trim().toLowerCase();
const injectionMode = String(env.MEMORY_AGENT_INJECTION_MODE ?? 'off').trim().toLowerCase();
const lifecycleMode = String(env.MEMORY_LIFECYCLE_ROLLOUT_MODE ?? 'off').trim().toLowerCase();
if (!readFlag(env, 'MEMORY_ENABLED')) {
issues.push({ code: 'memory_disabled', message: 'MEMORY_ENABLED must be 1' });
}
if (!readFlag(env, 'MEMORY_CANDIDATE_ENABLED')) {
issues.push({ code: 'candidate_disabled', message: 'MEMORY_CANDIDATE_ENABLED must be 1' });
}
if (candidateMode !== 'canary') {
issues.push({ code: 'candidate_mode', message: `MEMORY_CANDIDATE_MODE must be canary (current: ${candidateMode || 'off'})` });
}
if (readFlag(env, 'MEMORY_CANDIDATE_AUTO_ACCEPT_ALL')) {
issues.push({ code: 'auto_accept_all', message: 'MEMORY_CANDIDATE_AUTO_ACCEPT_ALL must stay 0 in Phase A' });
}
if (!readFlag(env, 'MEMORY_CANDIDATE_PERSISTENCE_ENABLED')) {
issues.push({ code: 'candidate_persistence', message: 'MEMORY_CANDIDATE_PERSISTENCE_ENABLED must be 1' });
}
if (!readFlag(env, 'MEMORY_AGENT_RESOLVE_ENABLED')) {
issues.push({ code: 'agent_resolve_disabled', message: 'MEMORY_AGENT_RESOLVE_ENABLED must be 1' });
}
if (injectionMode !== 'canary') {
issues.push({ code: 'injection_mode', message: `MEMORY_AGENT_INJECTION_MODE must be canary (current: ${injectionMode || 'off'})` });
}
if (agentCanaryUserIds.length === 0) {
issues.push({ code: 'agent_canary_users_missing', message: 'MEMORY_AGENT_CANARY_USER_IDS must list at least one user id' });
}
if (!readFlag(env, 'MEMORY_LIFECYCLE_ENABLED')) {
issues.push({ code: 'lifecycle_disabled', message: 'MEMORY_LIFECYCLE_ENABLED must be 1' });
}
if (lifecycleMode !== 'canary') {
issues.push({ code: 'lifecycle_mode', message: `MEMORY_LIFECYCLE_ROLLOUT_MODE must be canary (current: ${lifecycleMode || 'off'})` });
}
if (lifecycleUserIds.length === 0) {
issues.push({ code: 'lifecycle_users_missing', message: 'MEMORY_LIFECYCLE_ROLLOUT_USER_IDS must list at least one user id' });
}
if (agentCanaryUserIds.join(',') !== lifecycleUserIds.join(',')) {
issues.push({
code: 'canary_user_mismatch',
message: 'MEMORY_AGENT_CANARY_USER_IDS and MEMORY_LIFECYCLE_ROLLOUT_USER_IDS must match exactly',
});
}
if (!readFlag(env, 'MEMORY_PROMOTION_ENABLED')) {
issues.push({ code: 'promotion_disabled', message: 'MEMORY_PROMOTION_ENABLED must be 1' });
}
if (readFlag(env, 'MEMORY_LIFECYCLE_FORGETTING_ENABLED')) {
issues.push({ code: 'forgetting_enabled', message: 'MEMORY_LIFECYCLE_FORGETTING_ENABLED must stay 0 in Phase A' });
}
if (readFlag(env, 'MEMORY_LIFECYCLE_DECAY_ENABLED')) {
issues.push({ code: 'decay_enabled', message: 'MEMORY_LIFECYCLE_DECAY_ENABLED must stay 0 in Phase A' });
}
const pgConfigured = Boolean(String(env.MEMORY_PGVECTOR_DATABASE_URL ?? '').trim())
&& Boolean(String(env.MEMORY_PGVECTOR_EMBEDDING_MODULE ?? '').trim());
if (!pgConfigured) {
warnings.push({
code: 'pgvector_not_configured',
message: 'pgvector URL/embedding module not configured; recall will stay legacy-only until configured',
});
}
return {
ok: issues.length === 0,
issues,
warnings,
summary: {
candidateMode,
injectionMode,
lifecycleMode,
agentCanaryUserIds,
lifecycleUserIds,
pgConfigured,
},
};
}