> ## Documentation Index
> Fetch the complete documentation index at: https://docs.evermind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Reflection

> An offline background process that consolidates the memory fragments scattered across many conversations into a single, chronologically-organized narrative, soft-archiving the originals it replaces.

Reflection periodically consolidates the memory fragments scattered across many conversations into a single, chronologically-organized narrative. It runs offline in the background: it merges the Episodes inside one similarity cluster into one, resolves stale information by keeping the latest state, and soft-archives the originals it replaces, so memory gets more accurate and more compact with use, instead of piling up into noise.

## The Problem

After each conversation, EverOS extracts an **Episode**, a summary of a conversation segment. The same topic ends up split across several Episodes, each a point-in-time snapshot written with whatever was known at that moment:

> "Andrew has no pets yet" (Aug) → "Andrew adopted Toby" (Sep) → "Andrew also adopted Buddy" (Oct)

Three Episodes, each correct on its own, none complete, and one is now stale. Ask "how many pets does Andrew have?" and retrieval surfaces all three fragmented, overlapping, partly-contradictory pieces. Traditional approaches either keep everything (noise) or let new memories overwrite old ones (broken timeline, lost history). Reflection takes a third path: like a person reviewing periodically, it consolidates the cluster into one accurate, coherent narrative.

## How It Works

Reflection runs *offline*, separate from the live conversation path. The online path keeps extracting Episodes and clustering them; Reflection later consumes those clusters; it never sits between a user and a response.

```
Online (never blocked)                 Offline (scheduled)
──────────────────────                 ───────────────────
conversation → Episode → Cluster ────► Reflection consolidates the clusters
```

Geometric clustering groups semantically similar, time-adjacent Episodes into a **Cluster**. A scheduled run then consolidates one cluster at a time in four steps:

<Steps>
  <Step title="Select">
    Pick clusters worth consolidating: not yet consolidated and holding ≥ 2 members, or already consolidated and since joined by new members. At most 10 clusters per run, largest first.
  </Step>

  <Step title="Merge">
    Hand the cluster's Episodes to the LLM in chronological order and merge them into one narrative: preserve facts, resolve contradictions by keeping the latest state, restore the timeline, drop duplicates, end on the current state. A previously consolidated cluster is updated *incrementally*: only the new fragments are folded in.
  </Step>

  <Step title="Re-extract">
    The merged narrative is written to Markdown and triggers re-extraction of atomic facts, keeping derived data consistent.
  </Step>

  <Step title="Deprecate">
    The replaced originals get a `deprecated_by` pointer to the new narrative; cluster membership is updated and an audit record is written.
  </Step>
</Steps>

The merged narrative is, structurally, just an ordinary Episode marked `parent_type: cluster`, transparent to retrieval, with no search-pipeline changes. Default search excludes any memory carrying `deprecated_by`, so a query like "how many pets does Andrew have" hits only the one complete narrative:

> "Andrew initially had no pets. He later adopted a dog named Toby, then another named Buddy. He currently has two dogs."

## Quick Start

<Note>Examples assume EverOS is running on the default port `8000`. `<root>` is the EverOS memory root.</Note>

Reflection is **off by default**. Turn it on in `<root>/ome.toml`, one line:

```toml theme={null}
[strategies.reflect_episodes]
enabled = true
```

Once enabled it runs automatically **every Monday at 02:00**. That's how Reflection is meant to work, with nothing else to configure. To change the run time, add an optional `cron`:

```toml theme={null}
[strategies.reflect_episodes]
enabled = true
cron = "0 3 * * 0"        # optional: Sundays at 03:00
```

Config changes hot-reload (\~1–2s, no restart). Setting `enabled = false` stops the *next* run; a run already in progress finishes normally.

<Warning>
  **Don't run it too often.** Once a week at most is recommended. Each run is a lossy LLM merge, and repeatedly re-consolidating the same memories can make the narrative *worse*, not better, which is why the default is weekly.
</Warning>

### Where the result lands

Each run **appends one merged narrative** to the relevant user's Episode log and marks the fragments it replaces as archived. Markdown is the source of truth, so just open the user's Episode log:

```
<root>/<app>/<project>/users/<user_id>/episodes/episode-<date>.md
```

The new entry carries `parent_type: cluster` (Episodes from ordinary conversation are `parent_type: memcell`):

```markdown theme={null}
---
owner_id: u_andrew
timestamp: 2026-10-11T02:00:00+00:00
parent_type: cluster          # ← marks it as a Reflection merge product
parent_id: cl_a1b2c3d4e5f6
---
## Subject
Andrew's pet adoption journey

## Content
Andrew initially had no pets. He later adopted a dog named Toby, and then
adopted another dog named Buddy. He currently has two dogs.
```

### Triggering a run on demand

The normal mode is the scheduled run. For testing or debugging, trigger one immediately:

```bash theme={null}
curl -X POST http://127.0.0.1:8000/api/v1/ome/trigger \
  -H "Content-Type: application/json" \
  -d '{"name": "reflect_episodes", "timeout": 120, "force": true}'
# → { "status": "ok", "name": "reflect_episodes" }
```

`force: true` runs even when `enabled = false`. `status` is `"ok"` (finished) or `"timeout"` (didn't finish in time); an unknown strategy name returns `404`.

## Originals Are Retired, Not Deleted

This is the essential difference from crude overwriting, and it's what makes consolidation safe to trust. Replaced Episodes are never deleted; they are soft-archived:

* They no longer appear in default search (anything with `deprecated_by` is excluded), so they don't pollute the current conversation.
* Their Markdown frontmatter records the archive mapping (`deprecated_entries`), pointing at the narrative that replaced them.
* Because Markdown is the source of truth, every consolidation stays traceable to its original content, and the indexes can always be rebuilt from it.

Each run also writes one `reflection_report` audit record (`cluster_id`, `mode` = `init`/`update`, `source_count`, `merged_entry_id`, `created_at`), so consolidation history is fully inspectable.

## Prerequisites

Reflection's merge step calls an LLM, and re-clustering the merged narrative calls an embedding model. Configure both as you would for the rest of EverOS: an OpenAI-compatible `[llm]` and `[embedding]` block in `<root>/everos.toml`, or the matching `EVEROS_LLM__*` / `EVEROS_EMBEDDING__*` environment variables. If a provider is unavailable, the affected clusters are skipped and logged rather than failing the run.

## Configuration

Two files, two scopes: you write only the keys you want to override, and everything else falls back to shipped defaults.

| Setting                       | File          | Default     | Description                                             |
| ----------------------------- | ------------- | ----------- | ------------------------------------------------------- |
| `reflect_episodes.enabled`    | `ome.toml`    | `false`     | Set `true` to enable (the only setting needed)          |
| `reflect_episodes.cron`       | `ome.toml`    | `0 2 * * 1` | Run time as a cron expression (Mondays 02:00); optional |
| `clustering.threshold`        | `everos.toml` | `0.65`      | Clustering similarity threshold                         |
| `clustering.time_window_days` | `everos.toml` | `7.0`       | Clustering time window (days)                           |

## End-to-End Example

Enable Reflection, trigger a run by hand (in production it runs on schedule), then confirm that searching the topic returns the single merged narrative instead of the scattered fragments:

```bash theme={null}
BASE=http://127.0.0.1:8000/api/v1

# 1. Trigger a run now (force runs even while disabled; in production this fires on schedule)
curl -s -X POST "$BASE/ome/trigger" \
  -H "Content-Type: application/json" \
  -d '{"name": "reflect_episodes", "timeout": 120, "force": true}' | jq .
# → { "status": "ok", "name": "reflect_episodes" }

# 2. Search the topic: the hit is the consolidated narrative, not the old fragments
curl -s -X POST "$BASE/memory/search" \
  -H "Content-Type: application/json" \
  -d '{"query": "how many pets does Andrew have", "top_k": 5}' \
  | jq '.data.episodes[0] | {subject, episode, session_id}'
```

On a merged narrative the `session_id` is `null` (the aggregation-product marker), and `episode` holds the full consolidated text.

## Design Notes

Why Reflection is shaped this way:

* **Offline and scheduled**: merging is a heavy, lossy LLM operation, so it runs off the request path (conversations stay fast), and a weekly cadence lets enough new fragments accumulate to be worth re-merging.
* **Soft-archive, never delete**: originals stay in Markdown, so every consolidation is traceable and the indexes are always rebuildable.
* **A merged narrative is just an Episode**: reusing the Episode type means search and every downstream consumer keep working unchanged, so Reflection introduces no new retrieval path.
