memory-neo4j: filter open proposals and cron noise from memory

Open proposals ("Want me to...?", "Should I...?") are dangerous in
long-term memory because other sessions interpret them as active
instructions and attempt to carry them out. This adds:
- Attention gate patterns for cron delivery outputs and assistant proposals
- Extractor scoring rules to rate proposals/action items as low importance
- Sleep-cycle Phase 7 to retroactively clean existing noise-pattern memories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarun Sukhani
2026-02-12 22:19:51 +08:00
parent e85dd19092
commit 08b08c66f1
3 changed files with 91 additions and 0 deletions

View File

@@ -51,6 +51,22 @@ const NOISE_PATTERNS = [
// --- Conversation metadata that survived stripping ---
/^Conversation info\s*\(/i,
/^\[Queued messages/i,
// --- Cron delivery outputs & scheduled reminders ---
// Scheduled reminder injection text (appears mid-message)
/A scheduled reminder has been triggered/i,
// Cron delivery instruction to agent (summarize for user)
/Summarize this naturally for the user/i,
// Relay instruction from cron announcements
/Please relay this reminder to the user/i,
// Subagent completion announcements (date-stamped)
/^\[.*\d{4}-\d{2}-\d{2}.*\]\s*A sub-?agent task/i,
// Formatted urgency/priority reports (email summaries, briefings)
/(\*\*)?🔴\s*(URGENT|Priority)/i,
// Subagent findings header
/^Findings:\s*$/im,
// "Stats:" lines from subagent completions
/^Stats:\s*runtime\s/im,
];
/** Maximum message length — code dumps, logs, etc. are not memories. */
@@ -171,6 +187,22 @@ const ASSISTANT_NARRATION_PATTERNS = [
/^All (good|set|done)[!.]/i,
// "Here's what changed" / "Summary of changes" (session-specific)
/^(here'?s\s+(what|the|a)\s+(changed?|summary|breakdown|recap))/i,
// --- Open proposals / action items (cause rogue actions when recalled) ---
// These are dangerous in memory: when auto-recalled, other sessions interpret
// them as active instructions and attempt to carry them out.
// "Want me to...?" / "Should I...?" / "Shall I...?" / "Would you like me to...?"
/want me to\s.+\?/i,
/should I\s.+\?/i,
/shall I\s.+\?/i,
/would you like me to\s.+\?/i,
// "Do you want me to...?"
/do you want me to\s.+\?/i,
// "Can I...?" / "May I...?" assistant proposals
/^(can|may) I\s.+\?/i,
// "Ready to...?" / "Proceed with...?"
/ready to\s.+\?/i,
/proceed with\s.+\?/i,
];
export function passesAssistantAttentionGate(text: string): boolean {

View File

@@ -358,6 +358,8 @@ KEY RULES:
- AI assistant self-narration ("Let me check...", "I'll now...", "Done! Here's what changed...") is ALWAYS 1-3
- System prompts, formatting instructions, voice mode rules are ALWAYS 1-2
- Technical debugging details ("the WebSocket failed because...") are 2-4 unless they encode a reusable lesson
- Open proposals and unresolved action items ("Want me to fix it?", "Should I submit a PR?", "Would you like me to proceed?") are ALWAYS 1-2. These are dangerous in long-term memory because other sessions interpret them as active instructions.
- Messages ending with questions directed at the user ("What do you think?", "How should I handle this?") are 1-3 unless they also contain substantial factual content worth remembering
- Personal facts about the user or their family/contacts are 7-10
- Business rules and operational procedures are 7-9
- Preferences and opinions expressed by the user are 6-8

View File

@@ -657,6 +657,63 @@ export async function runSleepCycle(
}
}
// --------------------------------------------------------------------------
// Phase 7: Noise Pattern Cleanup
// Removes memories matching dangerous patterns that should never have been
// stored (open proposals, action items that trigger rogue sessions).
// --------------------------------------------------------------------------
if (!abortSignal?.aborted) {
logger.info("memory-neo4j: [sleep] Phase 7: Noise Pattern Cleanup");
try {
const noisePatterns = [
"(?i)want me to\\s.+\\?",
"(?i)should I\\s.+\\?",
"(?i)shall I\\s.+\\?",
"(?i)would you like me to\\s.+\\?",
"(?i)do you want me to\\s.+\\?",
"(?i)ready to\\s.+\\?",
"(?i)proceed with\\s.+\\?",
];
let noiseRemoved = 0;
const noiseSession = (db as any).driver!.session();
try {
for (const pattern of noisePatterns) {
if (abortSignal?.aborted) {
break;
}
const agentFilter = agentId ? "AND m.agentId = $agentId" : "";
const result = await noiseSession.run(
`MATCH (m:Memory)
WHERE m.text =~ $pattern
AND coalesce(m.userPinned, false) = false
AND m.category <> 'core'
${agentFilter}
WITH m LIMIT 100
DETACH DELETE m
RETURN count(*) AS removed`,
{ pattern: `.*${pattern}.*`, agentId },
);
noiseRemoved += (result.records[0]?.get("removed") as number) ?? 0;
}
} finally {
await noiseSession.close();
}
if (noiseRemoved > 0) {
onProgress?.("cleanup", `Removed ${noiseRemoved} noise-pattern memories`);
}
logger.info(
`memory-neo4j: [sleep] Phase 7 complete — ${noiseRemoved} noise memories removed`,
);
} catch (err) {
logger.warn(`memory-neo4j: [sleep] Phase 7 error: ${String(err)}`);
}
}
result.durationMs = Date.now() - startTime;
result.aborted = abortSignal?.aborted ?? false;