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>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user