<!-- Markdown mirror. Canonical: https://empryo.com/docs/tools/memory -->

# Memory

> Empryo's cross-session memory stores preferences, decisions, and gotchas, then injects up to 3 relevant ones per turn. Per-project or global, with soft-delete

[Video: Recall at work: file affinity, git co-change, bm25 and semantic fuse into one score, three memories surface, each badged with why.](https://empryo.com/media/code-memory-recall.mp4)

*Recall at work: file affinity, git co-change, bm25 and semantic fuse into one score, three memories surface, each badged with why.*

Memory is Empryo's long-term store. It remembers your preferences, your project's decisions, and the sharp edges you've hit before - and surfaces them automatically when they're relevant. You don't have to ask.

Memory is **per-project by default**, with an opt-in **global** scope for cross-project preferences (`"use bun not npm"`, `"be terse"`).

## What gets stored

Four kinds:

| Category | Example |
|---|---|
| `pref` | "use bun not npm", "be terse" |
| `decision` | "switched to zustand - redux was too much boilerplate" |
| `gotcha` | "JWT expiry uses container clock, drifts in prod" |
| `context` | "legacy/ deletes next sprint - don't touch" |

The agent writes these proactively when you share a preference, make a choice with a reason, or hit a non-obvious bug. You can also browse and edit them yourself.

[Video: Left: a rules file re-billed in full every turn, stale lines included. Right: recall injecting three summary stubs against the file being edited.](https://empryo.com/media/code-memory-prompt-bloat.mp4)

*Left: a rules file re-billed in full every turn, stale lines included. Right: recall injecting three summary stubs against the file being edited.*

## How recall works

Before each turn, Empryo looks at your prompt and which files you've been editing, then injects up to **3 relevant memories** silently. The agent sees them and adjusts. You don't have to type `/memory` or remember what's stored.

If nothing's relevant, nothing's injected - memory earns its prompt slot every turn or it doesn't take one.

## Browser: `/memory`

Type `/memory` to open the browser. Five tabs:

- **All** - every memory across project + global. Pin, soft-delete, or supersede per row.
- **Hidden** - soft-deleted entries, restorable forever.
- **Cleanup** - *Quick* (dupes + dead file refs) and *Stale* (low-signal candidates) for manual review.
- **SIM** - clusters of related memories.
- **Settings** - read & write scopes.

Nothing is ever truly deleted. Soft-delete only, restorable forever.

## Scopes

Two databases:

| Scope | Location | Use for |
|---|---|---|
| Project | `.empryo/memory.db` | code-specific decisions, file-scoped gotchas |
| Global | `~/.empryo/memory.db` | cross-project preferences, your style |

Read scope controls which DBs the agent sees:

```
all      project + global (default)
project  only this repo
global   only your preferences
none     turn recall off
```

## Pinning

Pin a memory to keep it from being surfaced in cleanup suggestions and to lift its ranking in recall. Useful for non-obvious rules you want to keep close.

## Languages

Memory works in any script - English, CJK, Arabic, Cyrillic, mixed-script identifiers. Searches use a word index plus a character n-gram index together, so paraphrases and non-Latin queries both hit.

## Commands

| Command | Description |
|---|---|
| `/memory` | Open the browser |

## Config

```json
{
  "memory": {
    "embeddingModel": "openai/text-embedding-3-small"
  }
}
```

Optional. Sets a real embedding provider for semantic recall. Default is a built-in offline embedder (hashbag-v2) that works without any API calls, good enough for word-overlap paraphrases.

### Do I need an API key?

Yes, if you set a provider model. The embedder is built from that provider in Empryo's own registry, so it uses **your** key for it, for `openai/text-embedding-3-small`, your OpenAI key. Without a valid key the embedder fails its startup probe and falls back to the offline embedder. Memory keeps working either way, and the reason is shown in `/memory` → Settings.

### Which models can I use?

Any `provider/model` id whose provider has an embedding API:

| Provider | Model |
|---|---|
| OpenAI | `openai/text-embedding-3-small`, `openai/text-embedding-3-large` |
| Google | `google/text-embedding-004` |
| Mistral | `mistral/mistral-embed` |
| OpenRouter | `openrouter/openai/text-embedding-3-small` |
| Vercel Gateway | `vercel_gateway/openai/text-embedding-3-small` |
| Ollama (local) | `ollama/nomic-embed-text`, `ollama/mxbai-embed-large` |
| LM Studio (local) | `lmstudio/nomic-embed-text` |

Local embeddings need no key at all, just pull the model in Ollama or LM Studio and name it here.

Providers with no embedding API (Anthropic, xAI, Groq, DeepSeek, Bedrock, LLM Gateway, proxy, copilot) fall back to the offline embedder and say so. Custom OpenAI-compatible providers work if their API exposes `/embeddings`.

You do not have to set this at all. When unset, it auto-resolves in order: `taskRouter.semantic`, a default for your active chat provider, then the offline embedder.

### How do I check it is working?

Open `/memory`, go to the **Settings** tab, and read the **Embedding** row. It shows the model actually embedding and where the choice came from. If a model you configured failed to wire, the row says so **and prints the reason** (missing key, no embedding API on that provider, network error) instead of quietly reading `offline`.
