Skip to main content
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:
1

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.
2

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.
3

Re-extract

The merged narrative is written to Markdown and triggers re-extraction of atomic facts, keeping derived data consistent.
4

Deprecate

The replaced originals get a deprecated_by pointer to the new narrative; cluster membership is updated and an audit record is written.
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

Examples assume EverOS is running on the default port 8000. <root> is the EverOS memory root.
Reflection is off by default. Turn it on in <root>/ome.toml, one line:
[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:
[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.
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.

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):
---
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:
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.
SettingFileDefaultDescription
reflect_episodes.enabledome.tomlfalseSet true to enable (the only setting needed)
reflect_episodes.cronome.toml0 2 * * 1Run time as a cron expression (Mondays 02:00); optional
clustering.thresholdeveros.toml0.65Clustering similarity threshold
clustering.time_window_dayseveros.toml7.0Clustering 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:
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.