# Caching and invalidation

How SurrealDB Agent Memory's semantic response cache works and how to manage memory lifecycle.

SurrealDB Agent Memory applies several complementary mechanisms to control what stays in memory, for how long, and at what quality. The semantic response cache eliminates redundant LLM calls for similar queries. Importance scoring governs which facts survive decay. Lifecycle sweeps enforce time-based expiry and scheduled degradation.

## Semantic response cache

When a recall query arrives, SurrealDB Agent Memory embeds the query text and checks it against a store of previously answered queries using HNSW approximate nearest-neighbour search (cosine similarity). If a stored query embedding matches the incoming one with a similarity score greater than **0.95**, SurrealDB Agent Memory returns the cached response directly without invoking the LLM.

The cache table is capped at **5000** entries (oldest evicted first) so index size stays bounded between invalidation sweeps.

The cache sits at **tier 2** of the query resolution pipeline, after a direct lookup (tier 1) but before the full hybrid retrieval and response generation path (tier 3). For workloads where many users ask semantically similar questions - "what is my current plan?", "which plan am I on?", "tell me my subscription tier?" - this eliminates the majority of response-generation tokens.

Cache behaviour in decision traces:

```json
{
  "query": "what plan am I on?",
  "tier": 2,
  "cached": true,
  "similarity": 0.97,
  "response": "You are on the Enterprise plan."
}
```

The cache is per-Context. Cache entries are scoped to the same dimensions as the query (user, org, project), so a cached answer for one user is never returned to another.

**`/chat` bypass:** when a session already has prior turns, `/chat` skips tier-2 reuse and always synthesises a fresh reply - the transcript window makes the answer session-dependent even when the semantic query matches a cached entry. Windowed turns are also withheld from seeding the cache. First-turn `/chat` calls and sessionless reads remain cache-eligible.

### Cache invalidation

Cache entries are invalidated when new facts that would affect the response are extracted. If a turn is processed that updates the user's plan, all cached query embeddings for that scope that relate to plan-type facts are evicted. Invalidation is automatic and does not require explicit intervention.

There is no manual flush verb - invalidation is driven entirely by the trace
graph as new facts land. To take the cache out of the path for a Context, set
`response_cache.enabled` to `false` in its config.

## Importance scoring

Every fact in SurrealDB Agent Memory carries an importance score between 0.0 and 1.0. The score governs how long the fact survives and how prominently it is weighted during retrieval. Initial scores are assigned by memory category:

| Category | Initial importance |
|---|---|
| Identity | 1.0 |
| Knowledge | 0.8 |
| Context | 0.5 |

**Identity** facts - user preferences, persistent attributes, long-term profile information - are assigned the maximum score and never decay (see below). **Knowledge** facts - extracted from documents or structured ingestion - start high because they represent deliberate, curated information. **Context** facts - extracted from ephemeral conversation turns - start lower, reflecting that most conversational detail is transient.

### Reinforcement on recall

Each time a fact is retrieved and returned to a caller, its importance score is multiplied by 1.1, capped at 1.0. This means frequently recalled facts reinforce themselves, while facts that are never retrieved gradually become less significant. The reinforcement reflects observed utility: if a fact keeps being surfaced, it is clearly relevant and should be retained.

## Importance decay

Importance scores decay on a per-category schedule. Decay runs as a background sweep at regular intervals (in standard deployments, nightly).

| Category | Decay factor per day | Notes |
|---|---|---|
| Context | × 0.95 | Aggressive - most conversational facts become negligible within a few weeks |
| Knowledge | × 0.995 | Slow - curated knowledge remains relevant for much longer |
| Identity | No decay | Identity facts persist indefinitely unless explicitly deleted |

A context-category fact starting at importance 0.5 decays to approximately 0.07 after 30 days, and to effectively zero after 60 days. It will be swept up by the auto-expiry TTL long before it reaches those values.

## Auto-expiry TTL

SurrealDB Agent Memory applies a default time-to-live (TTL) of **7 days** to context-category facts. After the TTL elapses, the fact is eligible for removal during the next lifecycle sweep. This prevents the memory layer from accumulating stale conversational detail indefinitely.

The TTL applies to the context category only. Knowledge and identity facts are not subject to the default TTL unless a retention policy overrides this.

## Retention policies

There are no declarative per-scope retention rules. Expiry is driven by the
**lifecycle sweeps** instead - `lifecycle.expire()` closes facts whose validity
has lapsed, and `lifecycle.decay()` ages importance scores. Run them on your own
schedule, or leave them to the background scheduler.

Trace rows are the one thing with a configurable retention window, set per
Context rather than per scope:

| Field | What it bounds |
| --- | --- |
| `trace_retention.decision_trace_days` | Reconciliation decision traces |
| `trace_retention.retrieval_trace_days` | Retrieval traces |
| `trace_retention.response_trace_days` | `/chat` and `/reflect` response traces |

Retention policies are enforced during the reconciliation sweep, not at write time. A fact written when a policy allows 30-day retention will be expired 30 days after creation, regardless of whether the policy is later changed.

## Lifecycle sweeps

Two background sweeps manage memory lifecycle:

### Expiry sweep

The expiry sweep removes facts that have exceeded their TTL. It runs as a background job in standard deployments. You can trigger it explicitly:

```python
await memory.lifecycle.expire()
```

```javascript
await memory.lifecycle.expire();
```

### Decay sweep

The decay sweep applies the per-category importance multipliers to all facts in the Context. It runs nightly in standard deployments. To trigger manually:

```python
await memory.lifecycle.decay()
```

```javascript
await memory.lifecycle.decay();
```

Manual triggering is useful during testing, when you want to fast-forward the decay state of a Context, or when managing self-hosted deployments where background job scheduling is under your control.

## Querying lifecycle state

To inspect the importance score and TTL of a specific fact:

```python
entity = await memory.entities.get("person", "alice")
for attr in entity.attributes:
    print(attr.key, attr.importance, attr.valid_until)
```

There is no endpoint that lists facts by upcoming expiry. Read the entities you
care about and filter on each attribute's `valid_until` yourself, or inspect the
whole Context state with `memory.state()`.
