memory-neo4j: tighten attention gate filters and add session skip patterns

Strip voice chat timestamps, conversation metadata blocks, and queued
message wrappers before the attention gate evaluates content. Expand
assistant narration patterns to catch UI interaction verbs, filler
responses ("I'm here", "Sure, tell me"), and page/step progress.
Add configurable autoCaptureSkipPattern and autoRecallSkipPattern
for bypassing memory on latency-sensitive sessions (e.g. voice).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarun Sukhani
2026-02-10 23:32:17 +08:00
parent a5ebbe4b55
commit e562ff4e31
5 changed files with 83 additions and 7 deletions

View File

@@ -47,6 +47,10 @@ const NOISE_PATTERNS = [
/^GatewayRestart:\s*\{/i,
// Background task completion reports
/^\[\w{3}\s+\d{4}-\d{2}-\d{2}\s.*\]\s*A background task/i,
// --- Conversation metadata that survived stripping ---
/^Conversation info\s*\(/i,
/^\[Queued messages/i,
];
/** Maximum message length — code dumps, logs, etc. are not memories. */
@@ -112,17 +116,19 @@ const MIN_ASSISTANT_WORD_COUNT = 10;
*/
const ASSISTANT_NARRATION_PATTERNS = [
// "Let me ..." / "Now let me ..." / "I'll ..." action narration
/^(ok[,.]?\s+)?(now\s+)?let me\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure)/i,
/^(ok[,.]?\s+)?(now\s+)?let me\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure|make|select|click|type|fill|open|close|switch|send|post|submit|edit|change|add|remove|write|save|upload)/i,
// "I'll ..." action narration
/^I('ll| will)\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure|execute|help|handle)/i,
/^I('ll| will)\s+(check|look|see|try|run|start|test|read|update|verify|fix|search|process|create|build|set up|examine|investigate|query|fetch|pull|scan|clean|install|download|configure|execute|help|handle|make|select|click|type|fill|open|close|switch|send|post|submit|edit|change|add|remove|write|save|upload|use|grab|get|do)/i,
// "Starting ..." / "Running ..." / "Processing ..." status updates
/^(starting|running|processing|checking|fetching|scanning|building|installing|downloading|configuring|executing|loading|updating)\s/i,
// "Good!" / "Great!" / "Perfect!" as opener followed by narration
/^(good|great|perfect|nice|excellent|awesome|done)[!.]?\s+(i |the |now |let |we |that )/i,
/^(starting|running|processing|checking|fetching|scanning|building|installing|downloading|configuring|executing|loading|updating|filling|selecting|clicking|typing|opening|closing|switching|navigating|uploading|saving|sending|posting|submitting)\s/i,
// "Good!" / "Great!" / "Perfect!" / "Done!" as opener followed by narration
/^(good|great|perfect|nice|excellent|awesome|done)[!.]?\s+(i |the |now |let |we |that |here)/i,
// Progress narration: "Now I have..." / "Now I can see..." / "Now let me..."
/^now\s+(i\s+(have|can|need|see|understand)|we\s+(have|can|need)|the\s)/i,
/^now\s+(i\s+(have|can|need|see|understand)|we\s+(have|can|need)|the\s|on\s)/i,
// Step narration: "Step 1:" / "**Step 1:**"
/^\*?\*?step\s+\d/i,
// Page/section progress narration: "Page 1 done!", "Page 3 — final page!"
/^Page\s+\d/i,
// Narration of what was found/done: "Found it." / "Found X." / "I see — ..."
/^(found it|found the|i see\s*[—–-])/i,
// Sub-agent task descriptions (workflow narration)
@@ -131,6 +137,16 @@ const ASSISTANT_NARRATION_PATTERNS = [
/^🔄\s*\*?\*?context reset/i,
// Filename slug generation prompts (internal tool use)
/^based on this conversation,?\s*generate a short/i,
// --- Conversational filler responses (not knowledge) ---
// "I'm here" / "I am here" filler: "I'm here to help", "I am here and listening", etc.
/^I('m| am) here\b/i,
// Ready-state: "Sure, (just) tell me what you want..."
/^Sure[,!.]?\s+(just\s+)?(tell|let)\s+me/i,
// Observational UI narration: "I can see the picker", "I can see the button"
/^I can see\s/i,
// A sub-agent task report (quoted or inline)
/^A sub-?agent task\b/i,
];
export function passesAssistantAttentionGate(text: string): boolean {

View File

@@ -31,8 +31,15 @@ export type MemoryNeo4jConfig = {
baseUrl: string;
};
autoCapture: boolean;
autoCaptureSkipPattern?: RegExp;
autoRecall: boolean;
autoRecallMinScore: number;
/**
* RegExp pattern to skip auto-recall for matching session keys.
* Useful for voice/realtime sessions where latency is critical.
* Example: /voice|realtime/ skips sessions containing "voice" or "realtime".
*/
autoRecallSkipPattern?: RegExp;
coreMemory: {
enabled: boolean;
maxEntries: number;
@@ -207,8 +214,10 @@ export const memoryNeo4jConfigSchema = {
"embedding",
"neo4j",
"autoCapture",
"autoCaptureSkipPattern",
"autoRecall",
"autoRecallMinScore",
"autoRecallSkipPattern",
"coreMemory",
"extraction",
"graphSearchDepth",
@@ -369,8 +378,16 @@ export const memoryNeo4jConfigSchema = {
},
extraction,
autoCapture: cfg.autoCapture !== false,
autoCaptureSkipPattern:
typeof cfg.autoCaptureSkipPattern === "string" && cfg.autoCaptureSkipPattern
? new RegExp(cfg.autoCaptureSkipPattern)
: undefined,
autoRecall: cfg.autoRecall !== false,
autoRecallMinScore: parseAutoRecallMinScore(cfg.autoRecallMinScore),
autoRecallSkipPattern:
typeof cfg.autoRecallSkipPattern === "string" && cfg.autoRecallSkipPattern
? new RegExp(cfg.autoRecallSkipPattern)
: undefined,
coreMemory: {
enabled: coreMemoryEnabled,
maxEntries: coreMemoryMaxEntries,

View File

@@ -986,6 +986,17 @@ const memoryNeo4jPlugin = {
return;
}
// Skip auto-recall for voice/realtime sessions where latency is critical.
// These sessions use short conversational turns that don't benefit from
// memory injection, and the ~100-300ms embedding+search overhead matters.
const sessionKey = ctx.sessionKey ?? "";
if (cfg.autoRecallSkipPattern && cfg.autoRecallSkipPattern.test(sessionKey)) {
api.logger.debug?.(
`memory-neo4j: skipping auto-recall for session ${sessionKey} (matches skipPattern)`,
);
return;
}
const agentId = ctx.agentId || "default";
// ~1000 chars keeps us safely within even small embedding contexts
@@ -1156,8 +1167,20 @@ const memoryNeo4jPlugin = {
return;
}
const agentId = ctx.agentId || "default";
// Skip auto-capture for sessions matching the skip pattern (e.g. voice sessions)
const sessionKey = ctx.sessionKey;
if (
cfg.autoCaptureSkipPattern &&
sessionKey &&
cfg.autoCaptureSkipPattern.test(sessionKey)
) {
api.logger.debug?.(
`memory-neo4j: skipping auto-capture for session ${sessionKey} (matches skipPattern)`,
);
return;
}
const agentId = ctx.agentId || "default";
// Fire-and-forget: run auto-capture asynchronously so it doesn't
// block the agent_end hook (which otherwise adds 2-10s per turn).

View File

@@ -72,10 +72,22 @@ export function stripMessageWrappers(text: string): string {
s = s.replace(/^\[media attached:[^\]]*\]\s*(?:To send an image[^\n]*\n?)*/i, "");
// System exec output blocks (may appear before Telegram wrapper)
s = s.replace(/^(?:System:\s*\[[^\]]*\][^\n]*\n?)+/gi, "");
// Voice chat timestamp prefix: [Tue 2026-02-10 19:41 GMT+8]
s = s.replace(
/^\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+GMT[+-]\d+\]\s*/i,
"",
);
// Conversation info metadata block (gateway routing context with JSON code fence)
s = s.replace(/Conversation info\s*\(untrusted metadata\):\s*```[\s\S]*?```\s*/g, "");
// Queued message batch header and separators
s = s.replace(/^\[Queued messages while agent was busy\]\s*/i, "");
s = s.replace(/---\s*Queued #\d+\s*/g, "");
// Telegram wrapper — may now be at start after previous strips
s = s.replace(/^\s*\[Telegram\s[^\]]+\]\s*/i, "");
// "[message_id: NNN]" suffix (Telegram)
s = s.replace(/\n?\[message_id:\s*\d+\]\s*$/i, "");
// "[message_id: UUID]" suffix (non-numeric Telegram/channel IDs)
s = s.replace(/\n?\[message_id:\s*[^\]]+\]\s*$/i, "");
// Slack wrapper — "[Slack <workspace> #channel @user] MESSAGE [slack message id: ...]"
s = s.replace(/^\s*\[Slack\s[^\]]+\]\s*/i, "");
s = s.replace(/\n?\[slack message id:\s*[^\]]*\]\s*$/i, "");

View File

@@ -188,6 +188,14 @@
},
"required": ["halfLifeDays"]
}
},
"autoRecallSkipPattern": {
"type": "string",
"description": "RegExp pattern to skip auto-recall for matching session keys (e.g. voice|realtime)"
},
"autoCaptureSkipPattern": {
"type": "string",
"description": "RegExp pattern to skip auto-capture for matching session keys (e.g. voice|realtime)"
}
},
"required": ["neo4j"]