feat: harden memory summary — fingerprint dedup, versioning, prompt injection defense

Summary hardening:
- SHA256 fingerprint of events content for deduplication
  (skips LLM call when events unchanged since last summary)
- Versioned summary storage: summary:agent:channel:vN keys
- Latest pointer: summary_latest:agent:channel for fast retrieval
- Prompt injection defense: sanitize event content before LLM,
  strip [SYSTEM]/[INTERNAL] markers, block "ignore instructions" patterns
- Anti-injection clause in SUMMARY_SYSTEM_PROMPT

Database fix:
- list_facts_by_agent: SQL filter by fact_prefix to only return chat_events
  (prevents summary/version facts from consuming LIMIT quota)
- Fixed NULL team_id issue in UNIQUE constraint (PostgreSQL NULL != NULL)
  using "__system__" sentinel for team_id in summary operations

Tested on NODE1: dedup works (same events → skipped), force=true bypasses.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Apple
2026-02-09 10:26:03 -08:00
parent 0cfd3619ea
commit 990e594a1d
2 changed files with 129 additions and 10 deletions

View File

@@ -521,14 +521,16 @@ class Database:
self,
agent_id: str,
channel_id: str = None,
limit: int = 60
limit: int = 60,
fact_prefix: str = "chat_event:"
) -> list:
"""List facts for an agent (any user), ordered by most recent."""
"""List facts for an agent (any user), ordered by most recent.
Only returns facts matching fact_prefix (default: chat_event:)."""
async with self.pool.acquire() as conn:
query = "SELECT * FROM user_facts WHERE agent_id = $1"
params = [agent_id]
query = "SELECT * FROM user_facts WHERE agent_id = $1 AND fact_key LIKE $2 || '%'"
params = [agent_id, fact_prefix]
if channel_id:
query += " AND fact_key LIKE '%' || $2 || '%'"
query += " AND fact_key LIKE '%' || $3 || '%'"
params.append(channel_id)
query += " ORDER BY updated_at DESC"
query += f" LIMIT ${len(params) + 1}"