{"slug": "build-a-privacy-filter-before-your-ai-agent-remembers-user-actions", "title": "Build a Privacy Filter Before Your AI Agent Remembers User Actions", "summary": "A developer outlines a privacy filter for AI agents that record user actions, emphasizing the need to capture minimal event streams and apply strict redaction and retention policies. The guide proposes a five-job filter covering event allowlisting, sensitive data detection, purpose binding, retention, and retrieval control, with a TypeScript example for implementation.", "body_md": "AI agents are starting to remember more than chats. They can watch clicks, typed text, app switches, browser context, files, tool calls, and workflow history. That memory can make an agent feel useful fast, but it can also turn a helpful feature into a quiet privacy incident.\n\nIf you are building an AI product, do not start with “how much can we capture?” Start with “what is the smallest event stream that still helps the user?” This guide shows a practical privacy filter you can place between raw user activity and agent memory.\n\nRecent AI tooling trends point in the same direction: agents are moving from chat boxes into operating systems, browsers, IDEs, customer support tools, analytics dashboards, and workflow automation platforms. The more useful the agent becomes, the more context it wants.\n\nThat creates a new engineering problem.\n\nTraditional app logs record requests and errors. Agent memory records intent, context, and behavior. A raw event can include:\n\nThis is not just observability. It is a privacy boundary.\n\nThe practical trigger is simple: computer-use agents and workflow agents now need history to resume work, personalize answers, and automate multi-step tasks. But developers, security reviewers, and buyers are asking harder questions about PII, retention, auditability, user consent, and whether agent traces can leak private business data.\n\nMost teams already have logs, traces, analytics events, and support transcripts. So when they add agent memory, they often reuse the same pattern:\n\nThat is easy to ship. It is also too broad.\n\nAgent memory needs a stricter path because it may be used to generate future answers or actions. A normal log line might be seen by engineers. A memory item might be read by a model, combined with other data, and used to make a decision.\n\nThe safer pattern is:\n\nRaw event → privacy filter → purpose check → redacted memory → retention policy → retrieval policy\n\nThe privacy filter is not a prompt. It is application code that decides what the agent is allowed to remember.\n\nA good privacy filter has five jobs.\n\n| Job | Question it answers | Example |\n|---|---|---|\n| Event allowlist | Should this event be captured at all? | Save “opened invoice page,” not every mouse coordinate. |\n| Sensitive data detection | Does the payload contain PII, secrets, or regulated data? | Detect emails, API keys, card-like numbers, tokens. |\n| Purpose binding | Why is this memory needed? | Resume task, improve support, audit approval. |\n| Retention control | How long can this memory live? | 48 hours for raw traces, 30 days for redacted task summaries. |\n| Retrieval control | Who or what can read it later? | Only the same user, tenant, role, and task type. |\n\nThe filter should run before indexing, summarization, embedding, analytics export, or model calls.\n\nDo not start with redaction. Start with event classes. Redaction helps when you must keep data. Classification helps you avoid collecting data in the first place.\n\nA simple event taxonomy might look like this:\n\n| Event class | Risk | Store by default? | Notes |\n|---|---|---|---|\n| Navigation event | Low | Yes, redacted | Page type, not full URL if it contains IDs. |\n| Tool call metadata | Medium | Yes | Store tool name, status, cost, policy result. |\n| User typed text | High | No | Store only if explicitly needed and redacted. |\n| Screen content | High | No | Prefer structured app state over screenshots. |\n| File content | High | No | Store references and hashes, not full content. |\n| Approval decision | Medium | Yes | Keep reviewer, action, timestamp, and reason. |\n| Secret or credential | Critical | Never | Block and alert if detected. |\n\nHere is a small TypeScript example:\n\n```\ntype EventClass =\n  | \"navigation\"\n  | \"tool_call\"\n  | \"typed_text\"\n  | \"screen_content\"\n  | \"file_content\"\n  | \"approval\"\n  | \"secret\";\n\ntype CaptureDecision = \"store\" | \"redact_then_store\" | \"summarize_only\" | \"drop\";\n\nconst capturePolicy: Record<EventClass, CaptureDecision> = {\n  navigation: \"redact_then_store\",\n  tool_call: \"store\",\n  typed_text: \"summarize_only\",\n  screen_content: \"drop\",\n  file_content: \"summarize_only\",\n  approval: \"store\",\n  secret: \"drop\",\n};\n```\n\nThis looks boring. That is the point. Privacy should not depend on a clever prompt at runtime.\n\nA raw event often contains too much context. Reduce it into a smaller shape before running PII detection.\n\nBad memory candidate:\n\n```\n{\n  \"type\": \"typed_text\",\n  \"value\": \"My card is 4242 4242 4242 4242 and my email is alex@example.com\",\n  \"url\": \"https://app.example.com/customers/cus_782/orders/ord_991\",\n  \"dom\": \"...full page text...\",\n  \"timestamp\": \"2026-08-15T03:30:00Z\"\n}\n```\n\nBetter memory candidate:\n\n```\n{\n  \"type\": \"task_signal\",\n  \"summary\": \"User entered payment-related information during checkout setup.\",\n  \"page_type\": \"checkout_settings\",\n  \"tenant_id\": \"tenant_123\",\n  \"user_id\": \"user_456\",\n  \"timestamp\": \"2026-08-15T03:30:00Z\"\n}\n```\n\nNotice what changed:\n\nReduction is the cheapest privacy win you can ship.\n\nUse multiple detectors. Regex is not enough, but regex is still useful.\n\nYou want to detect:\n\nExample filter:\n\n```\ntype Redaction = {\n  redacted: string;\n  findings: Array<{ type: string; count: number }>;\n};\n\nfunction redactSensitiveText(input: string): Redaction {\n  const findings: Redaction[\"findings\"] = [];\n  let text = input;\n\n  const patterns = [\n    { type: \"email\", regex: /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi },\n    { type: \"credit_card_like\", regex: /\\b(?:\\d[ -]*?){13,19}\\b/g },\n    { type: \"api_key_like\", regex: /\\b(?:sk|pk|ghp|xoxb|AKIA)[A-Za-z0-9_\\-]{16,}\\b/g },\n    { type: \"private_key\", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },\n  ];\n\n  for (const pattern of patterns) {\n    const matches = text.match(pattern.regex);\n    if (matches?.length) {\n      findings.push({ type: pattern.type, count: matches.length });\n      text = text.replace(pattern.regex, `[REDACTED_${pattern.type.toUpperCase()}]`);\n    }\n  }\n\n  return { redacted: text, findings };\n}\n```\n\nFor production, combine this with a structured PII service, domain-specific dictionaries, and field-level policies. The key is to store the findings separately from the raw value.\n\nA memory without a purpose becomes a future liability. Add a purpose field when the memory is created.\n\nCommon purposes:\n\n`resume_task`\n\n`support_debugging`\n\n`security_audit`\n\n`billing_dispute`\n\n`quality_evaluation`\n\n`personalization`\n\nEach purpose should control retention and retrieval.\n\n```\ntype MemoryPurpose =\n  | \"resume_task\"\n  | \"support_debugging\"\n  | \"security_audit\"\n  | \"billing_dispute\"\n  | \"quality_evaluation\"\n  | \"personalization\";\n\nconst retentionDays: Record<MemoryPurpose, number> = {\n  resume_task: 7,\n  support_debugging: 30,\n  security_audit: 180,\n  billing_dispute: 365,\n  quality_evaluation: 14,\n  personalization: 90,\n};\n```\n\nThis gives your product, legal, and engineering teams one shared control surface.\n\nIt also prevents a common failure mode: using data collected for debugging as long-term personalization memory.\n\nRaw traces and agent memory should not live in the same bucket.\n\nA useful split:\n\n**Raw event buffer**\n\nShort-lived, encrypted, tightly restricted, used for immediate debugging or user-visible replay.\n\n**Redacted memory store**\n\nLonger-lived, purpose-bound, searchable by the agent only through policy checks.\n\n**Audit ledger**\n\nAppend-only records of decisions: what was stored, why, which policy allowed it, and when it expires.\n\nThe agent should usually retrieve from the redacted memory store, not the raw buffer.\n\n```\n[User activity]\n      |\n      v\n[Raw event buffer: short TTL]\n      |\n      v\n[Privacy filter]\n      |\n      +--> [Drop / block / alert]\n      |\n      v\n[Redacted memory store]\n      |\n      v\n[Policy-checked retrieval]\n      |\n      v\n[Agent response or action]\n```\n\nThis structure makes deletion easier and audits less painful.\n\nConsent is not a checkbox on the settings page. It is runtime state.\n\nCheck consent when:\n\nExample:\n\n```\ntype ConsentState = {\n  userId: string;\n  tenantId: string;\n  allowAgentMemory: boolean;\n  allowPersonalization: boolean;\n  allowSupportReview: boolean;\n  revokedAt?: string;\n};\n\nfunction canStoreMemory(consent: ConsentState, purpose: MemoryPurpose): boolean {\n  if (!consent.allowAgentMemory || consent.revokedAt) return false;\n\n  if (purpose === \"personalization\") return consent.allowPersonalization;\n  if (purpose === \"support_debugging\") return consent.allowSupportReview;\n\n  return true;\n}\n```\n\nIf consent is revoked, new memory should stop immediately. Existing memory should either expire, be deleted, or become inaccessible depending on your product policy and legal requirements.\n\nA memory can be safe to store but unsafe to retrieve in a different context.\n\nBefore retrieving memory for an agent, check:\n\nThis prevents awkward bugs like a support agent retrieving billing context during a product tutorial, or a workspace agent pulling private notes into a shared channel.\n\nA retrieval policy can be simple:\n\n```\nfunction canRetrieveMemory(args: {\n  requesterTenantId: string;\n  requesterUserId: string;\n  memoryTenantId: string;\n  memoryUserId: string;\n  purpose: MemoryPurpose;\n  requestedPurpose: MemoryPurpose;\n  expiresAt: Date;\n}) {\n  if (args.requesterTenantId !== args.memoryTenantId) return false;\n  if (args.requesterUserId !== args.memoryUserId) return false;\n  if (args.purpose !== args.requestedPurpose) return false;\n  if (args.expiresAt.getTime() < Date.now()) return false;\n\n  return true;\n}\n```\n\nIn team products, replace the user equality check with a role and resource policy. Keep the default narrow.\n\nYou still need audit logs. Just do not put raw secrets in them.\n\nA good audit record includes:\n\n```\n{\n  \"memory_id\": \"mem_123\",\n  \"tenant_id\": \"tenant_123\",\n  \"user_id\": \"user_456\",\n  \"event_class\": \"typed_text\",\n  \"decision\": \"summarize_only\",\n  \"purpose\": \"resume_task\",\n  \"pii_findings\": [{ \"type\": \"email\", \"count\": 1 }],\n  \"policy_version\": \"privacy-filter-v4\",\n  \"created_at\": \"2026-08-15T03:30:00Z\",\n  \"expires_at\": \"2026-08-22T03:30:00Z\"\n}\n```\n\nThis record helps you answer:\n\nThat is much better than saving the whole raw payload and hoping nobody looks too closely.\n\nUse this as a build checklist for your first privacy filter.\n\n`drop`\n\nor `summarize_only`\n\n.Most privacy guides cover PII redaction, audit logs, or broad data governance. Agent memory needs one more layer: event design. Decide what to do with clicks, typed text, page context, tool calls, desktop actions, and model-readable summaries before they enter storage.\n\nAsk this before saving anything:\n\nWould this memory still feel reasonable if the user inspected it, exported it, or saw it during an incident review?\n\nIf the answer feels uncomfortable, reduce it.\n\nFor a small team, do not overbuild. Start with this:\n\n`agent_events`\n\ntable with short retention.`agent_memories`\n\ntable with redacted summaries only.`agent_memory_audit`\n\ntable for policy decisions.Schema sketch:\n\n```\nCREATE TABLE agent_memories (\n  id TEXT PRIMARY KEY,\n  tenant_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  purpose TEXT NOT NULL,\n  sensitivity TEXT NOT NULL,\n  summary TEXT NOT NULL,\n  metadata JSONB NOT NULL DEFAULT '{}',\n  policy_version TEXT NOT NULL,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n  expires_at TIMESTAMPTZ NOT NULL\n);\n\nCREATE INDEX agent_memories_lookup\nON agent_memories (tenant_id, user_id, purpose, expires_at);\n```\n\nThen test it like a security feature, not like a logging feature.\n\nAdd these to CI:\n\nThese tests will catch more real issues than another paragraph in your privacy policy.\n\nAgent memory is powerful because it compresses user context into future usefulness. That same compression can hide privacy mistakes if you capture too much, store it too long, or retrieve it in the wrong place.\n\nThe safest path is not “never remember anything.” That makes agents weak. The safest path is to remember less, explain why, expire it on purpose, and retrieve it only when the current task deserves it.\n\nBuild the privacy filter before the memory feature becomes popular. It is much easier to start narrow than to clean up a giant pile of raw history later.\n\nAn AI agent privacy filter is application logic that decides which user activity events can become agent memory. It classifies events, redacts sensitive data, checks consent, assigns a purpose, applies retention, and controls retrieval.\n\nUsually no. Raw actions such as typed text, full page content, screenshots, and file contents are high risk. Store reduced summaries, task state, tool metadata, or redacted memory instead.\n\nLogs are mainly used for debugging and operations. Agent memory may be retrieved by a model and used to generate future responses or actions. That makes purpose, consent, retention, and retrieval policy more important.\n\nDo not store raw secrets, API keys, session cookies, private keys, payment details, or regulated personal data unless you have a very specific, compliant reason. In most products, these should be blocked or heavily redacted.\n\nRetention depends on purpose. Task-resume memory may only need days. Support debugging may need weeks. Security audit records may need longer. Avoid one global retention window for every memory type.\n\nNo. Prompts can remind a model not to reveal sensitive data, but privacy enforcement should happen in code before storage, indexing, embedding, and retrieval.\n\nGive users visibility and control. Provide settings to disable memory, inspect saved memory, delete memory, and understand what the agent remembers and why.", "url": "https://wpnews.pro/news/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions", "canonical_source": "https://dev.to/jackm-singularity/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions-31fe", "published_at": "2026-08-15 03:35:01+00:00", "updated_at": "2026-08-15 03:41:19.760192+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "ai-safety", "ai-ethics", "developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions", "markdown": "https://wpnews.pro/news/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions.md", "text": "https://wpnews.pro/news/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions.txt", "jsonld": "https://wpnews.pro/news/build-a-privacy-filter-before-your-ai-agent-remembers-user-actions.jsonld"}}