# Memory System

MindRoom supports three memory backends:

- `mem0`: vector memory (semantic retrieval + extraction via Mem0)
- `file`: markdown memory files (`MEMORY.md` plus optional dated notes)
- `none`: disabled memory for stateless agents

Set the global default backend with `memory.backend`.
Override the backend per agent with `agents.<name>.memory_backend`.
When an agent uses `memory_backend: file`, its file memory lives in its canonical workspace root.
When an agent uses `memory_backend: none`, MindRoom skips prompt memory lookup, automatic memory persistence, and the explicit `memory` tool for that agent.
Use `agents.<name>.private` when one shared agent definition should keep file memory inside a requester-local private root.
`private` changes where private files live.
It does not switch the memory backend by itself.

OpenClaw compatibility uses this same backend selection; there is no separate OpenClaw-only memory engine.

Optional:
- `memory.team_reads_member_memory: true` allows team-context memory reads to include member agent scopes.

## Memory Scopes

| Scope | User ID Format | Description |
|---|---|---|
| Agent | `agent_<name>` | Agent preferences and durable user context |
| Team | `team_<agent1>+<agent2>+...` | Shared team conversation memory |

Notes:
- Team IDs are sorted agent names joined by `+`.

## Backend: `mem0`

`mem0` keeps the existing behavior:
- semantic retrieval before response
- automatic extraction after turns
- storage in Chroma-backed Mem0 collections

Example:

```yaml
memory:
  backend: mem0
  embedder:
    provider: openai
    config:
      model: text-embedding-3-small
      credentials_service: embedder # Optional strict credential binding
      dimensions: null             # Optional: embedding dimension override (e.g., 256)
```

The `openai` embedder resolves its API key in this order: explicit `memory.embedder.config.api_key`; then the service named by `memory.embedder.config.credentials_service`; or, when no service is named, the legacy dedicated `embedder` credential (seeded from `EMBEDDER_API_KEY` or `EMBEDDER_API_KEY_FILE`) followed by the shared `openai` provider key.
An explicit `credentials_service` is a strict binding: if that service has no key, MindRoom does not silently borrow another OpenAI key. Use it when your embedding endpoint needs a different key than chat, voice, or another OpenAI-compatible model.
When none of these are set, MindRoom sends a non-secret placeholder key so keyless local OpenAI-compatible endpoints keep working, while a keyed endpoint rejects the placeholder with a loud classified auth failure.
When the embedder rejects its credential, semantic search degrades loudly instead of returning fake-empty results: memory tool output and the per-turn prompt carry the classified cause, and `/api/health` reports an `embedder` block until a request succeeds again.

Fully local embedder example:

```yaml
memory:
  backend: mem0
  embedder:
    provider: sentence_transformers
    config:
      model: sentence-transformers/all-MiniLM-L6-v2
```

MindRoom auto-installs the optional `sentence_transformers` extra the first time this provider is used.

Ollama embedder example:

```yaml
memory:
  backend: mem0
  embedder:
    provider: ollama
    config:
      model: nomic-embed-text
      host: http://localhost:11434
```

Supported embedder providers: `openai`, `ollama`, `huggingface`, `sentence_transformers`.

### Memory LLM

The memory system uses an LLM for extraction. Configure it with `memory.llm`:

```yaml
memory:
  llm:
    provider: ollama    # ollama, openai, or anthropic
    config:
      model: llama3.2
```

Supported LLM providers: `ollama` (default), `openai`, `anthropic`.

## Backend: `none`

`none` disables MindRoom's built-in durable memory for the effective agent.

Global example:

```yaml
memory:
  backend: none
```

Shorthand example:

```yaml
memory: none
```

Per-agent stateless override:

```yaml
memory:
  backend: mem0

agents:
  scratch:
    display_name: Scratch
    role: Stateless scratchpad agent
    memory_backend: none
```

Disabled memory does not disable Agno Learning.
Set `learning: false` separately if you also want to disable learning.

## Backend: `file`

`file` keeps memory in markdown files and treats files as source-of-truth.

Example:

```yaml
memory:
  backend: file
  file:
    max_entrypoint_lines: 200
  search:
    mode: keyword
    include:
      - memory/**/*.md
    include_entrypoint: false
```

`memory.file.path` is an optional fallback root for file-memory paths.
It does not relocate canonical agent file memory (which always lives under the agent's workspace root).
It can affect team file memory when the resolution determines the configured path should be used.
`memory.search` controls how `search_memories` reads file-backed agent memory.
`mode: keyword` scans `MEMORY.md` and `memory/**/*.md` directly.
`mode: semantic` builds a lazy per-agent or per-requester vector index under `mindroom_data/memory_search_db/` and uses `memory.embedder`.
`include` contains root-relative glob patterns for semantic search below the effective file-memory root.
The default semantic include `memory/**/*.md` searches dated memory files and excludes `MEMORY.md`.
Set `include_entrypoint: true` only if you also want `MEMORY.md` returned by semantic search.
MindRoom already preloads `MEMORY.md` into the prompt, so the default avoids duplicate retrieval.
That preload is introduced by a header naming the file's absolute path and stating that it is inlined automatically every turn, so the agent does not re-read it to recall its own memory.
`max_entrypoint_lines` caps how much of the file is inlined; when it truncates, the preload ends with a marker reporting the included and total line counts, the active cap, and the path to read for the omitted lines.
Semantic fallback uses the same fixed `MEMORY.md` and `memory/**/*.md` corpus as keyword mode rather than the semantic include patterns.
Semantic mode applies to the agent's own file-memory scope.
Team-visible file memory is still keyword searched.
File memory is already searchable on demand through `search_memories`.
When its agent-scoped semantic index is ready, configured file memory is also listed as a read-only source in `search_knowledge_base`.
The knowledge surface is semantic-only and does not provide keyword fallback, team-visible memory, or memory IDs, so use `search_memories` when those capabilities matter.

Per-agent override example:

```yaml
memory:
  backend: mem0

agents:
  coder:
    display_name: Coder
    role: Write and review code
    memory_backend: file
    memory_search:
      mode: semantic
      include:
        - memory/**/*.md
      include_entrypoint: false
```

Omitted `memory_search` fields inherit from `memory.search`.

For shared agents, file memory now lives directly under `agents/<name>/workspace/`.
For requester-private agents, file memory lives directly under the effective private root.
Use `private` when you need per-requester file-memory isolation.

Private instance example:

```yaml
agents:
  mind:
    display_name: Mind
    role: A persistent personal AI companion
    memory_backend: file
    private:
      per: user
      root: mind_data
      template_dir: ./mind_template
```

In this setup, each requester gets their own private `mind_data/` root inside a canonical private-instance state root in shared storage.
When `memory_backend: file` is enabled, that private root becomes the agent's effective file-memory root.
If `./mind_template/` contains `MEMORY.md` and `memory/`, those files are copied into each private root on first use and then remain editable per requester.
Later runs backfill newly added scaffold files without overwriting requester edits.
MindRoom does not invent `MEMORY.md` or `memory/` for private agents.
Put those files in your template directory if you want them scaffolded into each private root.
If `memory_backend` is not `file`, `private` still creates private files and directories, but it does not make file memory active.
Use `private` for requester-isolated workspaces.

### File layout

Agent file memory is stored under each agent's canonical workspace root:

- `agents/<agent>/workspace/MEMORY.md`
- `agents/<agent>/workspace/memory/YYYY-MM-DD.md`

Team file memory is mirrored under each participating agent's storage directory:

- `agents/<agent>/memory_files/team_<sorted_members>/MEMORY.md`
- `agents/<agent>/memory_files/team_<sorted_members>/memory/YYYY-MM-DD.md`

## File Auto-Flush Worker

When the effective backend is `file` for at least one agent, you can enable background auto-flush:

```yaml
memory:
  backend: file
  auto_flush:
    enabled: true
    flush_interval_seconds: 1800
    idle_seconds: 120
    max_dirty_age_seconds: 600
    stale_ttl_seconds: 86400
    max_cross_session_reprioritize: 5
    retry_cooldown_seconds: 30       # Cooldown before retrying a failed extraction
    max_retry_cooldown_seconds: 300   # Upper bound for retry cooldown backoff
    batch:
      max_sessions_per_cycle: 10
      max_sessions_per_agent_per_cycle: 3
    extractor:
      no_reply_token: NO_REPLY
      max_messages_per_flush: 20
      max_chars_per_flush: 12000
      max_extraction_seconds: 30
      include_memory_context:
        memory_snippets: 5
        snippet_max_chars: 400
```

High-level behavior:

1. Turns mark sessions dirty.
2. Background worker picks eligible dirty sessions in bounded batches.
3. Worker runs a model-driven extraction (not keyword heuristics) to produce durable memories.
4. If extractor returns `NO_REPLY`, nothing is written.
5. Successful writes append to memory files via normal memory APIs.

## UI Configuration

The Dashboard **Memory** page supports:
- backend selection (`mem0`, `file`, or `none`)
- team/member read toggle (`team_reads_member_memory`)
- embedder provider/model/host
- file backend settings (`path`, `max_entrypoint_lines`)
- search settings (`mode`, `include`, `include_entrypoint`)
- auto-flush settings (intervals, idle/age thresholds, retries)
- batch sizing
- extractor settings (`no_reply_token`, message/char/time limits, `include_memory_context` dedupe bounds)

Save from the Memory page to persist changes to `config.yaml`.
Use the Dashboard **Agents** page to set an agent-specific **Memory Backend** override.

## Optional Memory Tool

For explicit agent-controlled memory operations, add the `memory` tool:

```yaml
agents:
  assistant:
    tools: [memory]
```

This exposes `add_memory`, `search_memories`, `list_memories`, `get_memory`, `update_memory`, and `delete_memory`.
Persisted IDs and file-backed `file:<path>:<line>` IDs support read, update, and delete operations.
Semantic file-memory results use read-only `semantic:<source_file>:<rank>` locators.

## Agno Learning

MindRoom integrates Agno's built-in Learning system, which lets agents learn and adapt from conversations.
Learning is separate from the memory backends above — it uses Agno's own SQLite-backed storage in each agent's state root (`learning/`).

### Configuration

```yaml
defaults:
  learning: true          # Enable learning for all agents (default: true)
  learning_mode: always   # "always" (extract after every turn) or "agentic" (agent decides via tool)
```

Per-agent override:

```yaml
agents:
  assistant:
    learning: false       # Disable learning for this agent
  research:
    learning_mode: agentic  # Agent controls when to learn
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `learning` | bool | `true` | Enable Agno Learning for the agent |
| `learning_mode` | string | `always` | `always`: automatic extraction after every turn. `agentic`: agent decides via tool when to learn |

Agents inherit `learning` and `learning_mode` from `defaults` unless explicitly overridden.
Disabled agents do not create or update learning state.
Learning data persists in `agents/<name>/learning/<agent>.db` within the agent's state root.
