# How are memories updated, consolidated or forgotten?

> Agent memory layers — a good answer covers: Update/merge logic; summarisation or consolidation; decay, TTL and deletion; versioning or history.

Canonical page: https://llms-technical-reviews.com/memory/q/lifecycle/

## Verdict

Graphiti handles changing facts best: a contradicting fact closes the old edge with an `invalid_at` date instead of deleting it. Hindsight and Honcho go furthest at consolidation, with background LLM jobs that merge facts into higher-level beliefs.

**Background consolidation.** After each retain, [Hindsight](/p/hindsight/) asks an LLM to create, update or delete "observations", and tracks `proof_count` and source ids for each one. Recency affects ranking only, and nothing expires. [Honcho](/p/honcho/) "dreams" after 50 new explicit facts and at least 8 hours since the last dream. A deduction agent can delete conclusions and rewrite the peer card, and an induction agent adds generalisations. Its surprisal sampling is off by default. [Cognee](/p/cognee/) re-extracts only the changed chunks on `update()`, and its `improve()` pass feeds sessions and feedback back into the graph. [MemMachine](/p/memmachine/) asks an LLM which features to keep once a tag holds 20. The `consolidation_threshold` setting is never passed through, so the threshold is always 20. Features keep no history.

**Time-stamped invalidation.** [Graphiti](/p/graphiti/) closes contradicted edges with `invalid_at` and `expired_at`. Its `remove_episode` is partial: it does not restore edges that the removed episode had invalidated.

**Append and count.** [Mem0](/p/mem0/) only adds memories. It logs explicit updates and deletes in SQLite, and decay is a hosted-only feature. [Memori](/p/memori/) increments `num_times` when a fact repeats exactly, has no contradiction handling, and deletes per entity, all or nothing. [claude-mem](/p/claude-mem/) never forgets by age. Its ACT-R ranking and cross-session dedup are both off by default. [MemOS](/p/memos/) merges conflicting memories only when its reorganizer is enabled, which it is not by default. A feedback endpoint can rewrite memories through an LLM.

**Hidden.** [Supermemory](/p/supermemory/)'s types show version chains, `isForgotten` and `forgetAfter`, but the logic runs on the closed server.

Pick: Graphiti when you need to ask what was true at a given time.
Pick: Hindsight or Honcho for memory that consolidates on its own.
Pick: Mem0 or Memori when your application owns corrections.

## Per-project answers

### thedotmack/claude-mem (answered)

**Update/merge**: Memory items support full field-level replacement via `MemoryItemsRepository.update` (`src/storage/sqlite/memory-items.ts:181-243`), which merges provided fields over existing values. The dedup system (`src/services/dedup/nearDuplicate.ts:49-76`) classifies observation pairs into two tiers: **Tier-0 exact** — normalized titles match → silent auto-merge; **Tier-1 candidate** — IDF-weighted TF-IDF cosine similarity above a configurable threshold and no IDF-veto fires → flagged as a candidate for review but never auto-merged (`src/services/dedup/tfidfCosine.ts:20-34`). The IDF veto (`src/services/dedup/idfVeto.ts`) prevents merging when a rare discriminating token appears in only one of the two observations. **Consolidation**: Session summaries condense the entire session arc into request/investigated/learned/completed/next_steps fields (`processSessionSummaryResponse` at `src/server/generation/processGeneratedResponse.ts:199-256`). These coexist alongside per-event observations rather than replacing them. **Cleanup and deletion**: The Postgres schema enforces cascade deletes — deleting a team or project cascades to sessions, events, observations, and generation jobs (`src/storage/postgres/schema.ts:91-238`). The `CleanupV12_4_3` utility (`src/services/infrastructure/CleanupV12_4_3.ts:27-236`) runs one-time cleanup of stale observer sessions with automatic database backup before deletion. FTS maintenance (`src/services/infrastructure/FtsMaintenance.ts:60-84`) merges FTS5 b-trees incrementally to reclaim space from deleted rows. **No explicit TTL/decay**: The system relies on recency-based selection and optional ACT-R reinforcement scoring (`src/services/reinforcement/rank.ts:50-63`) to naturally demote older observations, rather than expiring them. Observations remain in storage indefinitely but are excluded from the context budget window by newer content. Generation jobs have configurable `max_attempts` with exponential backoff (`src/server/generation/processGeneratedResponse.ts:556-560`).


Citations: [src/storage/sqlite/memory-items.ts:181-243](https://github.com/thedotmack/claude-mem/blob/0a803bdedb09b00fc7fcfdc3a55a4e75c55c3952/src/storage/sqlite/memory-items.ts#L181-L243) · [src/services/dedup/nearDuplicate.ts:49-76](https://github.com/thedotmack/claude-mem/blob/0a803bdedb09b00fc7fcfdc3a55a4e75c55c3952/src/services/dedup/nearDuplicate.ts#L49-L76) · [src/services/dedup/tfidfCosine.ts:14-34](https://github.com/thedotmack/claude-mem/blob/0a803bdedb09b00fc7fcfdc3a55a4e75c55c3952/src/services/dedup/tfidfCosine.ts#L14-L34) · [src/services/infrastructure/FtsMaintenance.ts:60-84](https://github.com/thedotmack/claude-mem/blob/0a803bdedb09b00fc7fcfdc3a55a4e75c55c3952/src/services/infrastructure/FtsMaintenance.ts#L60-L84) · [src/services/infrastructure/CleanupV12_4_3.ts:27-76](https://github.com/thedotmack/claude-mem/blob/0a803bdedb09b00fc7fcfdc3a55a4e75c55c3952/src/services/infrastructure/CleanupV12_4_3.ts#L27-L76) · [src/server/generation/processGeneratedResponse.ts:199-256](https://github.com/thedotmack/claude-mem/blob/0a803bdedb09b00fc7fcfdc3a55a4e75c55c3952/src/server/generation/processGeneratedResponse.ts#L199-L256)

### mem0ai/mem0 (answered)

**Update/merge** — `update()` (`mem0/memory/main.py:1829-1881`) retrieves the existing memory by ID, re-embeds the new text, freshens the payload (recomputing hash and lemmatized text, updating `updated_at`), and calls `vector_store.update()`. Identity keys (`user_id`/`agent_id`/`run_id`) are immutable — metadata attempts to change them are silently dropped with a warning via `_strip_identity_keys` (`mem0/memory/main.py:143-162`). If the text changed, the old entity links are removed and new entities extracted via `_link_entities_for_memory`. The change is recorded in the SQLite history table as an UPDATE event with the old text preserved.

**Deletion** — `delete()` (`mem0/memory/main.py:1883-1902`) retrieves the existing memory, removes it from the vector store, and strips its memory_id from linked entity records via `_remove_memory_from_entity_store`. The history entry records the event as a DELETE with `is_deleted=1`. `delete_all()` (`mem0/memory/main.py:1904-1958`) lists memories in batches of 1000 and deletes each one, with a guard against infinite loops on stores that cap list() results.

**Expiration/TTL** — the `expiration_date` parameter on `add()` and `update()` sets a YYYY-MM-DD date in the payload. `_payload_is_expired()` (`mem0/memory/main.py:442-451`) checks whether that date is before today (UTC). Expired memories are excluded from `search()` and `get_all()` unless `show_expired=True`. There is no automated background cleanup — expired records remain in the store but are hidden.

**No decay or consolidation** — the OSS SDK explicitly rejects decay features. Calling `project.update(decay=True)` raises `ValueError` with the message that decay is a hosted-platform-only feature. Neither consolidation, summarization, nor automated forgetting across memories exists in the self-hosted path.

**Versioning/history** — `history(memory_id)` (`mem0/memory/main.py:1960-1973`) queries the SQLite `history` table for every ADD, UPDATE, and DELETE event on a given memory, ordered by timestamp. Each record stores the old and new text, the event type, timestamps, actor_id, and role.

**Reset** — `reset()` (`mem0/memory/main.py:2144-2174`) deletes the vector store collection and SQLite database, then recreates both from scratch. The entity store (if initialized) is also reset.


Citations: [mem0/memory/main.py:1829-1881](https://github.com/mem0ai/mem0/blob/c93420c49a6b14c3d446bdb156d96811908fd90a/mem0/memory/main.py#L1829-L1881) · [mem0/memory/main.py:1883-1902](https://github.com/mem0ai/mem0/blob/c93420c49a6b14c3d446bdb156d96811908fd90a/mem0/memory/main.py#L1883-L1902) · [mem0/memory/main.py:2144-2174](https://github.com/mem0ai/mem0/blob/c93420c49a6b14c3d446bdb156d96811908fd90a/mem0/memory/main.py#L2144-L2174) · [mem0/memory/main.py:427-451](https://github.com/mem0ai/mem0/blob/c93420c49a6b14c3d446bdb156d96811908fd90a/mem0/memory/main.py#L427-L451) · [mem0/memory/main.py:467-472](https://github.com/mem0ai/mem0/blob/c93420c49a6b14c3d446bdb156d96811908fd90a/mem0/memory/main.py#L467-L472) · [mem0/memory/storage.py:150-255](https://github.com/mem0ai/mem0/blob/c93420c49a6b14c3d446bdb156d96811908fd90a/mem0/memory/storage.py#L150-L255)

### vectorize-io/hindsight (answered)

Lifecycle management has three main paths. **Consolidation** (`consolidation/consolidator.py:1425-1544`) runs as a background job after retain, processing unconsolidated memories (`memory_units` where `consolidated_at IS NULL`). It groups new facts, fetches existing observations by embedding similarity, and calls the LLM with a consolidation prompt (`prompts.py:1-80`) that decides whether to CREATE a new observation, UPDATE an existing one (merging new evidence), or DELETE a stale one. Observations are stored in `memory_units` with `fact_type='observation'`, tracking `proof_count` and `source_memory_ids`. The consolidator also runs LLM-based semantic dedup (`_dedup_adjudicate`, line 275) with a configurable `consolidation_dedup_threshold` that probes nearest neighbours and asks the LLM if the new observation is truly distinct (using `_DEDUP_PROMPT`, line 178-197). **Dedup at write time** via the entity resolver uses trigram similarity clustering (`entity_resolver.py:79-80`) to merge near-identical entity names within a batch. **Mental model (knowledge page) refresh** turns a synthetic document into a curated summary: it runs `reflect` on a configured `source_query` scope and writes the result as markdown content in the `knowledge_pages` table. Pages can refresh on a cron schedule or after each consolidation round. **Explicit delete** (`delete_memory_unit`, `memory_engine.py:10936-10985`) cascades to `memory_links`, `unit_entities`, and invalidates dependent observations. **Direct invalidation** (`invalidate_memory` endpoint) marks a memory unit as invalidated in `invalidated_memory_units` so consolidation can re-process its dependents. There is no TTL-based decay — the recency boost in reranking (`reranking.py:35-53`) is a query-time scoring adjustment (linear decay over 365 days, exponential with 90-day half-life, or disabled), not a deletion mechanism. Version history is tracked on mental models (`mental_model_versions` table) and on the consolidation history JSONB column per observation.


Citations: [hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py:1425-1544](https://github.com/vectorize-io/hindsight/blob/8830bbb3bcde8600431b7ffcfea62074113f263f/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py#L1425-L1544) · [hindsight-api-slim/hindsight_api/engine/consolidation/prompts.py:1-80](https://github.com/vectorize-io/hindsight/blob/8830bbb3bcde8600431b7ffcfea62074113f263f/hindsight-api-slim/hindsight_api/engine/consolidation/prompts.py#L1-L80) · [hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py:120-200](https://github.com/vectorize-io/hindsight/blob/8830bbb3bcde8600431b7ffcfea62074113f263f/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py#L120-L200) · [hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py:200-210](https://github.com/vectorize-io/hindsight/blob/8830bbb3bcde8600431b7ffcfea62074113f263f/hindsight-api-slim/hindsight_api/engine/consolidation/consolidator.py#L200-L210) · [hindsight-api-slim/hindsight_api/engine/search/reranking.py:35-78](https://github.com/vectorize-io/hindsight/blob/8830bbb3bcde8600431b7ffcfea62074113f263f/hindsight-api-slim/hindsight_api/engine/search/reranking.py#L35-L78) · [hindsight-api-slim/hindsight_api/engine/memory_engine.py:10936-10985](https://github.com/vectorize-io/hindsight/blob/8830bbb3bcde8600431b7ffcfea62074113f263f/hindsight-api-slim/hindsight_api/engine/memory_engine.py#L10936-L10985)

### getzep/graphiti (answered)

**Update/merge** happens at write time via the dedup pipeline: when `resolve_extracted_nodes()` finds a semantic match in the graph, the existing node's `attributes` are merged with the new extraction using an overlay merge (`apply_capped_attributes()` in `attribute_utils.py` with `merge_mode='overlay'`), preserving prior values that the new extraction omitted. Edge resolution (`resolve_extracted_edge()` in `edge_operations.py:638`) works similarly but uses `merge_mode='replace'` for typed edge attributes. Edges accumulate provenance (`episodes` list) across extractions.

**Summarization** refines entity descriptions. `extract_attributes_from_nodes()` (`node_operations.py:744`) first calls per-entity attribute extraction via an LLM prompt, then batched summary generation via `_extract_entity_summaries_batch()`. Nodes with short summaries have edge facts appended directly (no LLM call); longer ones are partitioned into flights of 30 and sent to the LLM's `extract_summaries_batch` prompt. The per-entity summary cap is `MAX_SUMMARY_CHARS` (configurable in `text_utils.py`). **Saga summarization** (`Graphiti.summarize_saga()`, graphiti.py:499) incrementally updates a saga's running summary using a wall-clock watermark (`last_summarized_at`) and episode-time watermark (`last_summarized_episode_valid_at`), so backfilled episodes are caught on subsequent runs without re-summarizing everything.

**Invalidation / forgetting** uses bi-temporal markers. Edges carry `valid_at` (when a fact became true) and `invalid_at` (when it stopped being true). When `resolve_extracted_edge()` identifies contradictions, the older edge's `invalid_at` is set to the new edge's `valid_at`, and `expired_at` records when the system recognized the change. The `resolve_edge_contradictions()` function (`edge_operations.py:547`) handles the temporal algebra — an old edge is invalidated only if its validity window precedes the new edge's window. Nodes can be removed via `remove_episode()` (graphiti.py:1892), which cascade-deletes edges and nodes that were created solely by the removed episode. Whole groups are deletable via `Node.delete_by_group_id()`. There is no TTL-based automatic decay — forgetting is explicit through invalidation or deletion.


Citations: [graphiti_core/utils/maintenance/node_operations.py:744-800](https://github.com/getzep/graphiti/blob/689de295c209631405c00e19e0af4f9735142f13/graphiti_core/utils/maintenance/node_operations.py#L744-L800) · [graphiti_core/utils/maintenance/edge_operations.py:638-875](https://github.com/getzep/graphiti/blob/689de295c209631405c00e19e0af4f9735142f13/graphiti_core/utils/maintenance/edge_operations.py#L638-L875) · [graphiti_core/utils/maintenance/edge_operations.py:547-583](https://github.com/getzep/graphiti/blob/689de295c209631405c00e19e0af4f9735142f13/graphiti_core/utils/maintenance/edge_operations.py#L547-L583) · [graphiti_core/graphiti.py:499-632](https://github.com/getzep/graphiti/blob/689de295c209631405c00e19e0af4f9735142f13/graphiti_core/graphiti.py#L499-L632) · [graphiti_core/graphiti.py:1892-1920](https://github.com/getzep/graphiti/blob/689de295c209631405c00e19e0af4f9735142f13/graphiti_core/graphiti.py#L1892-L1920) · [graphiti_core/nodes.py:178-234](https://github.com/getzep/graphiti/blob/689de295c209631405c00e19e0af4f9735142f13/graphiti_core/nodes.py#L178-L234)

### topoteretes/cognee (answered)

Updates use chunk-level incremental diffing (cognee/api/v1/update/update.py:28): new content is diffed against stored text via a paragraph-anchored multi-region diff, only changed chunks are re-chunked and re-extracted, unchanged chunks keep their ids, entities and embeddings. Falls back to full delete+re-add+cognify when preconditions fail. The document keeps its data_id across every path. Consolidation runs through improve() (cognee/api/v1/improve/improve.py:101) with nine ordered stages: feedback_weights, persist_session_qa, persist_agent_traces, extract_agent_context, distill_sessions, update_user_preferences, build_truth_subspace, triplet_enrichment, global_context_index (cognee/modules/improve/registry.py:27-37). Each stage gates first with zero LLM calls; improvements serialize via the improve lock. Deletion via forget() (cognee/api/v1/forget/forget.py:22) supports data_id, dataset, everything=True, and memory_only=True, deleting across graph, vector, and session backends with pipeline status reset for re-cognify. DataPoint.valid_to provides bi-temporal fact supersession.


Citations: [cognee/api/v1/update/update.py:28-45](https://github.com/topoteretes/cognee/blob/b32d8afc59e1064d9291b9828a8a147be9cc8bab/cognee/api/v1/update/update.py#L28-L45) · [cognee/api/v1/improve/improve.py:101-170](https://github.com/topoteretes/cognee/blob/b32d8afc59e1064d9291b9828a8a147be9cc8bab/cognee/api/v1/improve/improve.py#L101-L170) · [cognee/modules/improve/registry.py:27-37](https://github.com/topoteretes/cognee/blob/b32d8afc59e1064d9291b9828a8a147be9cc8bab/cognee/modules/improve/registry.py#L27-L37) · [cognee/api/v1/forget/forget.py:22-90](https://github.com/topoteretes/cognee/blob/b32d8afc59e1064d9291b9828a8a147be9cc8bab/cognee/api/v1/forget/forget.py#L22-L90) · [cognee/infrastructure/engine/models/DataPoint.py:79-85](https://github.com/topoteretes/cognee/blob/b32d8afc59e1064d9291b9828a8a147be9cc8bab/cognee/infrastructure/engine/models/DataPoint.py#L79-L85)

### supermemoryai/supermemory (answered)

Memory lifecycle is tracked through version chains and state flags. **Versioning**: each `MemoryEntry` has `version`, `isLatest`, `parentMemoryId`, and `rootMemoryId` (`apps/mcp/src/shared/types.ts:140-147`). The `history` array (line 151) stores prior versions as `MemoryEntryHistory` objects (lines 125-137). **Forgetting**: the `isForgotten` boolean flag marks memories as forgotten (`apps/mcp/src/shared/types.ts:143`). The `forgetMemory` method (`apps/mcp/src/server/client/index.ts:181-239`) first tries exact-match deletion via the SDK, then falls back to a similarity search (threshold 0.85, line 200) to find a semantically matching memory to forget. **TTL/expiration**: `forgetAfter` (nullable string) and `forgetReason` (nullable string) fields on `DocumentMemoryEntry` (`apps/mcp/src/shared/types.ts:81-82`) suggest scheduled expiration, though the MCP server does not implement the expiry logic itself — that happens in the upstream API. **Update/merge**: the `add_memory` tool (`apps/mcp/src/server/tools/add-memory.ts`) always creates new memory; there is no merge or consolidation logic in this MCP server. The `updatesMemoryId` and `nextVersionId` fields on the graph type (`apps/mcp/src/widget/views/Graph.tsx:99-100`) confirm a linked-list version pattern. Decay and deletion are server-side concerns delegated to the hosted API.


Citations: [apps/mcp/src/shared/types.ts:139-152](https://github.com/supermemoryai/supermemory/blob/ac2180498223cce078eb263b9d240fdc2f5c3548/apps/mcp/src/shared/types.ts#L139-L152) · [apps/mcp/src/shared/types.ts:75-90](https://github.com/supermemoryai/supermemory/blob/ac2180498223cce078eb263b9d240fdc2f5c3548/apps/mcp/src/shared/types.ts#L75-L90) · [apps/mcp/src/server/client/index.ts:181-239](https://github.com/supermemoryai/supermemory/blob/ac2180498223cce078eb263b9d240fdc2f5c3548/apps/mcp/src/server/client/index.ts#L181-L239) · [apps/mcp/src/widget/views/Graph.tsx:83-104](https://github.com/supermemoryai/supermemory/blob/ac2180498223cce078eb263b9d240fdc2f5c3548/apps/mcp/src/widget/views/Graph.tsx#L83-L104)

### MemoriLabs/Memori (answered)

Updates, consolidation, and deletion follow distinct patterns:

**Incremental updates via upsert.** Both entity facts and knowledge-graph triples use PostgreSQL's `ON CONFLICT` mechanism. For entity facts, the unique constraint is `(entity_id, uniq)`, where `uniq` is a deterministic hash of the fact content (`memori/storage/drivers/postgresql/_driver.py:257,278-280`). When the same fact is re-extracted from a later conversation, the row is not duplicated — instead `num_times` is incremented and `date_last_time` is refreshed. The same applies to knowledge-graph triples (constraint on `entity_id, subject_id, predicate_id, object_id`, `memori/storage/drivers/postgresql/_driver.py:572-574`) and process attributes (constraint on `process_id, uniq`).

**Conversation summaries.** The `AdvancedAugmentation` response can include a `conversation.summary` string, which is written back to `memori_conversation.summary` via the `conversation.update` write path (`memori/memory/augmentation/augmentations/memori/_augmentation.py:280-285`). This summary is then used as context for future augmentations: when selecting messages to send to the API, the augmentation handler reads the existing summary and sends it alongside the latest user/assistant turn rather than the full history (`memori/memory/augmentation/augmentations/memori/_augmentation.py:53-84,135-137`).

**Deletion.** There is an explicit `Recall.delete_entity_memories()` method (`memori/memory/recall.py:193-211`) that deletes all facts and knowledge-graph entries for a given entity. In BYODB mode, the driver's `delete_by_entity()` cascades through the schema: deleting from `memori_entity_fact` (which cascades to `memori_entity_fact_mention`) and from `memori_knowledge_graph` (which cascades to orphaned subjects, predicates, and objects).

**No TTL or decay.** This version of the codebase does not implement automatic time-to-live, decay, or consolidation merging of facts. There is no background job scanning for stale memories. The `recall_relevance_threshold` (default 0.1) provides a static retrieval-time filter, but facts persist indefinitely once written. Individual scaffolding for versioning (the `uuid` and `date_updated` columns on every table) exists in the schema but is not leveraged for history tracking at the application layer.


Citations: [memori/storage/drivers/postgresql/_driver.py:250-290](https://github.com/MemoriLabs/Memori/blob/574b1ea3e876f100ef82c37817d603eb7e258e59/memori/storage/drivers/postgresql/_driver.py#L250-L290) · [memori/storage/drivers/postgresql/_driver.py:418-580](https://github.com/MemoriLabs/Memori/blob/574b1ea3e876f100ef82c37817d603eb7e258e59/memori/storage/drivers/postgresql/_driver.py#L418-L580) · [memori/memory/augmentation/augmentations/memori/_augmentation.py:53-84](https://github.com/MemoriLabs/Memori/blob/574b1ea3e876f100ef82c37817d603eb7e258e59/memori/memory/augmentation/augmentations/memori/_augmentation.py#L53-L84) · [memori/memory/recall.py:193-211](https://github.com/MemoriLabs/Memori/blob/574b1ea3e876f100ef82c37817d603eb7e258e59/memori/memory/recall.py#L193-L211)

### MemTensor/MemOS (answered)

Versioned archival + Dream consolidation + scheduler. NodeHandler.resolve() (handler.py:76-188) merges conflicting memories with MERGED_TO edge, sets originals to archived. Each TextualMemoryMetadata has version + history of ArchivedTextualMemory (item.py:109-128). GraphStructureReorganizer (reorganizer.py:81-107) runs background queue for add/remove/merge/update + periodic clustering under LLM-summarized parents. Scheduler has MEM_ARCHIVE_TASK_LABEL (task_schemas.py:38). Dream plugin (off by default): DreamSignalStore (signal_store.py:8-62) accumulates signals, triggers at 100, runs Contextualizer->MotiveFormation->DirectRecall->ConsolidationReasoning->DiarySummary->Persistence. DreamMemoryLifecycle (dream/types.py:67-80): last_hit_at, hit_count, usefulness_score, invalidated_by_feedback. maintenance.py (dream/maintenance.py:1-56) documents 3 planned strategies (not implemented): stale-by-TTL, low-usefulness archive, invalidation-by-feedback. Feedback API (mem_feedback/feedback.py:315) archives replaced memories.


Citations: [src/memos/memories/textual/tree_text_memory/organize/handler.py:76-188](https://github.com/MemTensor/MemOS/blob/a7367d07e55db61099f7b4e2c1108bc5831a24f3/src/memos/memories/textual/tree_text_memory/organize/handler.py#L76-L188) · [src/memos/memories/textual/item.py:109-128](https://github.com/MemTensor/MemOS/blob/a7367d07e55db61099f7b4e2c1108bc5831a24f3/src/memos/memories/textual/item.py#L109-L128) · [src/memos/memories/textual/tree_text_memory/organize/reorganizer.py:81-107](https://github.com/MemTensor/MemOS/blob/a7367d07e55db61099f7b4e2c1108bc5831a24f3/src/memos/memories/textual/tree_text_memory/organize/reorganizer.py#L81-L107) · [src/memos/dream/signal_store.py:8-62](https://github.com/MemTensor/MemOS/blob/a7367d07e55db61099f7b4e2c1108bc5831a24f3/src/memos/dream/signal_store.py#L8-L62) · [src/memos/dream/types.py:67-80](https://github.com/MemTensor/MemOS/blob/a7367d07e55db61099f7b4e2c1108bc5831a24f3/src/memos/dream/types.py#L67-L80) · [src/memos/dream/maintenance.py:1-56](https://github.com/MemTensor/MemOS/blob/a7367d07e55db61099f7b4e2c1108bc5831a24f3/src/memos/dream/maintenance.py#L1-L56)

### plastic-labs/honcho (answered)

Memory lifecycle has four phases. **Creation**: The Deriver extracts observations on message creation; each observation gets `times_derived=1`. On repeat writes, instead of inserting duplicates the `times_derived` counter is incremented (`src/crud/document.py:693-695`), strengthening the fact's reinforcement signal. **Consolidation via Dreaming**: The **Dreamer** (`src/dreamer/orchestrator.py`) runs scheduled 'dreams' using two specialist agents. The **DeductionSpecialist** (`src/dreamer/specialists.py`) reads existing explicit observations and produces **deductive** conclusions via a tool-using agent loop (tools: `get_recent_observations`, `search_memory`, `search_messages`, `create_observations_deductive`, `delete_observations`, `update_peer_card`). The **InductionSpecialist** produces **inductive** generalizations (patterns, preferences, personality) from both explicit and deductive conclusions. Surprisal-based prioritization (`src/dreamer/surprisal.py`) pre-filters observations: it builds a tree structure from embeddings, computes geometric surprisal scores ranking how anomalous or informative each observation is, and feeds the highest-surprisal observations as hints to the specialist agents — focusing the expensive dream cycle on the most 'interesting' or novel findings. **Reasoning trees** (`src/dreamer/trees/`) link each conclusion to its premises via `document_sources` table and `source_ids` references, enabling `get_reasoning_chain` traversal. **Deletion and cleanup**: Documents are soft-deleted via `deleted_at` timestamp (`src/crud/document.py:901-948`). The `ReconcilerScheduler` (`src/reconciler/scheduler.py`) periodically runs `cleanup_soft_deleted_documents()` which hard-deletes rows soft-deleted >5 minutes ago and removes their vectors from the external store. The reconciler also heals missed vector syncs and cleans stale queue items. **Summarization**: Parallel to memory extraction, the `summarizer.py` creates two-tier session summaries (short every ~20 messages, long every ~60) stored on the session metadata, providing a compressing lifecyle for long-running conversations.

> **Editor's note.** Correction: surprisal sampling is disabled by default (DREAM.SURPRISAL.ENABLED=false), and src/dreamer/trees/ holds the spatial trees used for surprisal scoring; conclusion-to-premise links are stored in the document_sources table.

Citations: [src/dreamer/orchestrator.py:1-37](https://github.com/plastic-labs/honcho/blob/11b22bff8b3fdd3d23d90a53357a418e0cf2e084/src/dreamer/orchestrator.py#L1-L37) · [src/dreamer/specialists.py:1-50](https://github.com/plastic-labs/honcho/blob/11b22bff8b3fdd3d23d90a53357a418e0cf2e084/src/dreamer/specialists.py#L1-L50) · [src/dreamer/surprisal.py:46-80](https://github.com/plastic-labs/honcho/blob/11b22bff8b3fdd3d23d90a53357a418e0cf2e084/src/dreamer/surprisal.py#L46-L80) · [src/crud/document.py:1469-1549](https://github.com/plastic-labs/honcho/blob/11b22bff8b3fdd3d23d90a53357a418e0cf2e084/src/crud/document.py#L1469-L1549) · [src/utils/summarizer.py:1-58](https://github.com/plastic-labs/honcho/blob/11b22bff8b3fdd3d23d90a53357a418e0cf2e084/src/utils/summarizer.py#L1-L58)

### MemMachine/MemMachine (answered)

MemMemory manages memory lifecycle through three distinct mechanisms: **update**, **consolidation**, and **eviction/forgetting**. **Updates** are LLM-driven: when a new episode arrives, `IngestionService._process_single_set()` (`semantic_ingestion.py:122`) loads existing features for each category, then calls `llm_feature_update()` with both the old profile and new message. The LLM returns semantically-meaningful mutations as add/delete commands. **Consolidation** runs asynchronously for semantic memory when a tag accumulates >=20 features (the `consolidation_threshold`). `_deduplicate_features()` (`semantic_ingestion.py:447`) calls `llm_consolidate_features()` with all features grouped by tag, plus an LLM prompt that can merge, split, and delete redundant entries. The prompt instructs: "If enough memories share similar features... delete all of them and create a single new memory containing a list." The consolidation is a keep-list design — memories not in `keep_memories` are deleted. In short-term memory, consolidation is **summarization** (`ShortTermMemoryConsolidator`, `short_term_memory.py:467`): when the deque's total message length exceeds `message_capacity` (default 64000 chars), the oldest episodes are evicted and a background `_run_summary_loop()` incrementally generates a summary via LLM. The summary is prepended on retrieval. If the summary prompt exceeds the context window, episodes are binary-split recursively until they fit. **Forgetting/deletion** is explicit: episodes can be deleted by UID (`delete_episodes()`) in both short-term and long-term memory. Session-level drops (`drop_session_partition()`) on the event backend delete the entire VectorStore collection and SegmentStore partition. Semantic features can be deleted individually, by set+category filter, or by set_id. The background ingestion uses a backoff-retry loop (`semantic_memory.py:783`) that catches transient failures, and marks messages as ingested only after successful processing. There is no built-in TTL-based decay for episodic memory, and no explicit versioning/history for features — updates replace semantically, and the storage layer stores only the latest value per feature.


Citations: [packages/server/src/memmachine_server/semantic_memory/semantic_ingestion.py:122-145](https://github.com/MemMachine/MemMachine/blob/c1c04bcaf775035f4aa1e61f466e0d9f1a6c6a96/packages/server/src/memmachine_server/semantic_memory/semantic_ingestion.py#L122-L145) · [packages/server/src/memmachine_server/semantic_memory/semantic_ingestion.py:447-465](https://github.com/MemMachine/MemMachine/blob/c1c04bcaf775035f4aa1e61f466e0d9f1a6c6a96/packages/server/src/memmachine_server/semantic_memory/semantic_ingestion.py#L447-L465) · [packages/server/src/memmachine_server/semantic_memory/semantic_llm.py:128-148](https://github.com/MemMachine/MemMachine/blob/c1c04bcaf775035f4aa1e61f466e0d9f1a6c6a96/packages/server/src/memmachine_server/semantic_memory/semantic_llm.py#L128-L148) · [packages/server/src/memmachine_server/episodic_memory/short_term_memory/short_term_memory.py:467-535](https://github.com/MemMachine/MemMachine/blob/c1c04bcaf775035f4aa1e61f466e0d9f1a6c6a96/packages/server/src/memmachine_server/episodic_memory/short_term_memory/short_term_memory.py#L467-L535) · [packages/server/src/memmachine_server/episodic_memory/long_term_memory/long_term_memory.py:423-457](https://github.com/MemMachine/MemMachine/blob/c1c04bcaf775035f4aa1e61f466e0d9f1a6c6a96/packages/server/src/memmachine_server/episodic_memory/long_term_memory/long_term_memory.py#L423-L457)
