Two-tier memory subsystem — short-term working storage, long-term pattern lookup, and security

Overview

Attune's memory subsystem gives agents two tiers of storage behind one API: short-term working memory (fast, TTL-expiring, optionally Redis-backed) and long-term pattern memory (durable, searchable, classified). Security runs before anything durable is written — auto-classification, PII scrubbing, secrets detection, and at-rest encryption for sensitive patterns.

The recommended entry point is UnifiedMemory, which composes both tiers and the security layer behind a single object. It is environment-aware: a MemoryConfig auto-detected from the environment chooses backends (in-process for development, Redis for production) so the same code runs in either. For custom backends, two protocols — MemoryBackend and SearchableMemoryBackend — define the short-term and searchable contracts.

You reach memory these ways:

UnifiedMemory's public methods are synchronous — call them directly, no await.

Concepts

Two tiers, one object

UnifiedMemory(user_id=...) exposes both tiers:

Construction is environment-aware

UnifiedMemory takes a required user_id, an optional config (MemoryConfig, default auto-detected from the environment), and an optional access_tier (AccessTier, default CONTRIBUTOR). MemoryConfig.from_environment() reads ATTUNE_-prefixed variables (EMPATHY_ also accepted) — ATTUNE_ENV selects development, staging, or production, which in turn drives Redis and storage defaults. Construct one with explicit settings via UnifiedMemory(user_id="me", config=MemoryConfig(...)).

Security runs before durable writes

When you persist a pattern, classification and scrubbing run first. auto_classify=True (the default) assigns a ClassificationPUBLIC, INTERNAL, or SENSITIVE — from the content and pattern type; PII is scrubbed and credential-like content is flagged before storage; SENSITIVE patterns are encrypted at rest. You can pass an explicit classification to override the auto-assignment. Reads honor the caller's access_tier unless you set check_permissions=False on recall_pattern.

Capabilities tell you what the backend can do

A deployment's backend may or may not support real-time updates, distribution across processes, or durable persistence. UnifiedMemory surfaces this: get_capabilities() returns a dict[str, bool], and supports_realtime(), supports_distributed(), and supports_persistence() answer individually. health_check() and get_backend_status() report runtime state. Check capabilities before relying on, say, cross-process coordination.

Custom backends implement a protocol

MemoryBackend is a @runtime_checkable Protocol for short-term stores: stash(key, value, ttl, agent_id), retrieve(key, agent_id), delete(key), keys(pattern), is_connected(), get_stats(), close(), plus supports_realtime() / supports_distributed(). SearchableMemoryBackend extends it with search(query, limit), remember(content, ...), promote(session_id), prune(max_age_days), and recent(limit). Any class implementing the methods satisfies the protocol — no base class to inherit. (Note these protocol signatures — stash(key, value, ttl, agent_id) — differ from UnifiedMemory's own stash(key, value, ttl_seconds).)

Static project context

ClaudeMemoryLoader resolves CLAUDE.md files at enterprise, user, and project levels and merges them via its load_all_memory() method. Which levels load is controlled by the MemoryConfig fields load_enterprise_memory / load_user_memory / load_project_memory (not loader methods). This is the static counterpart to the read/write tiers above.

Notes & tips