Skip to main content

memlife

Memory that degrades gracefully. Not another pile that grows forever.

PyPI Python License

Current version: 0.6.4

What's New

0.6.4

  • main branch synchronised with the 0.6.2 and 0.6.3 release tags so GitHub source tree and README match PyPI.

0.6.3

  • README and version metadata are now kept in sync with releases.

0.6.2

  • Graph relationship traversal now follows incoming edges as well as outgoing edges.
  • Entity canonicalisation is case-insensitive, preventing duplicate James/james nodes.
  • retrieve() accepts debug=True and SyncMemoryStore.store_mention_triple() was added for API parity.

0.6.1

  • Migration idempotency fix, fact_conflict_threshold wired correctly, and graph-integrated retrieval scoring improvements.
  • Polyphonic recall respects the score cutoff and debug output includes linking triples.

0.6.0

  • Embedding cache — content-addressable, LRU-capped embedding cache.
  • Automatic entity extraction — deterministic, zero-LLM extraction of entities from facts, episodes, and journal entries.
  • Graph-integrated retrieval — retrieve() boosts candidates linked to entities mentioned in the query.

memlife is a four-tier lifecycle memory system for AI agents. Instead of treating memory as a monotonically growing database, every entry has a lifecycle — facts decay, journal entries retire, superseded data is pruned, and nothing accumulates forever.

The four tiers:

  • Episodes — raw events (what happened)
  • Facts — durable truths (what I know)
  • Journal — reflected beliefs (what I believe)
  • Decay/Prune — confidence fades, stale entries retire, GC cleans up

Why memlife

Every other memory system accumulates. Facts never expire. Confidence never decays. Stale conventions become unquestioned truths. Recall quality degrades over time.

memlife solves this. Memory should be like human memory — it fades, it gets revised, it gets pruned. Not a database that grows until it breaks.

Install

pip install memlife

With adapters (optional):

pip install memlife[ollama]       # Ollama embedder + chat
pip install memlife[openai]       # OpenAI embedder + chat
pip install memlife[sentence-transformers]  # Local embeddings
pip install memlife[mcp]          # MCP server

Quickstart (30 seconds, zero dependencies)

import asyncio
from memlife import MemoryStore, MemoryConfig, DummyEmbedder

async def main():
    store = MemoryStore(
        config=MemoryConfig(db_path="./memlife.db", embedding_model="dummy"),
        embedder=DummyEmbedder(),
    )

    # Store an episode (something happened)
    store.remember(task="User asked about deployment", outcome="success")

    # Store a fact (durable truth)
    await store.store_fact("User deploys via GitHub Actions", confidence=0.8)

    # Store an entity relationship (fact-like but structured)
    store.store_triple("User", "deploys_via", "GitHub Actions", confidence=0.8)

    # Retrieve relevant memories (unified scoring across all layers)
    context = await store.retrieve("deployment")
    print(context)

    store.close()

asyncio.run(main())

No Ollama, no OpenAI, no API key. The DummyEmbedder uses bag-of-words vectors — similar sentences get positive cosine similarity. The full lifecycle — store, retrieve, decay, GC, and entity graph — works without any LLM. Only structured extraction and reflection need a model.

The Lifecycle

┌───────────┐     reflection      ┌───────────┐
│  EPISODE  │ ──────────────────▶│  JOURNAL  │
│  (event)  │   LLM synthesises   │ (belief)  │
└─────┬─────┘   observations &   └─────┬─────┘
│  extract triples   │
      │                                 │
      │ store_fact() / store_triple()  │ confidence decay
      ▼                                 │ (configurable)
┌───────────┐    recall bumps    ┌─────▼─────┐
│   FACT    │ ◀────────────────  │  RETIRE   │
│  (truth)  │   confidence +0.05 │ (floor)   │
└─────┬─────┘                    └─────┬─────┘
│    │
│    │ entity graph (triples)
│    ▼
┌───────────────┐
│ TRIPLE / GRAPH│
│(subject-pred- │
│ object + prov)│
└───────┬───────┘
      │
      │ revise / supersede             │ GC prunes
      ▼                                ▼
┌───────────┐                   ┌───────────┐
│ SUPERSEDED│  configurable     │  PRUNED   │
│ (replaced)│ ──────────────────▶│ (deleted) │
└───────────┘   retention       └───────────┘

UNIFIED SCORE = relevance × confidence × recency
Applied across ALL layers before every response.

NO-LLM MODE: store + retrieve + decay + GC + entity graph work
without any model. Only reflection and structured extraction need an LLM.

No-LLM Mode

The store, retrieval, decay, GC, entity graph, and embedding versioning all work without any LLM. Only the reflection loop and structured extraction need a model.

from memlife import MemoryStore, MemoryConfig

store = MemoryStore(config=MemoryConfig(db_path="./memlife.db"))
store.remember(task="something happened", outcome="success")

# retrieve() is async — use SyncMemoryStore or asyncio.run():
import asyncio
context = asyncio.run(store.retrieve("something"))
store.close()

This makes memlife useful for testing, offline agents, and lightweight deployments where you don't want to manage model access.

Core Concepts

Episodes

Episodes are raw events. They're cheap to store and useful for recall, but the system doesn't treat them as durable truth. Over time, reflection can promote observations from episodes into facts or journal entries.

store.remember(
    task="User asked about deployment",
    outcome="success",
    confidence=0.9,
)

Facts

Facts are durable truths with explicit confidence. They decay on disuse, get bumped on recall, and can be revised or superseded.

await store.store_fact("User deploys via GitHub Actions", confidence=0.8)

Entity Graph (Triples)

Structured relationships between entities. Triples behave like facts but participate in graph traversal.

store.store_triple("User", "deploys_via", "GitHub Actions", confidence=0.8)

Journal

The journal holds reflected beliefs — higher-level observations the system synthesises from episodes and facts during reflection.

await store.reflect()  # or schedule it

Decay, Retirement, and GC

Confidence decays over time. Entries that fall below a floor retire. Superseded entries age out. Garbage collection prunes them according to retention policy. Everything is configurable.

Retrieval

retrieve() scores across all four tiers using a unified function:

score = relevance × confidence × recency

This means a highly relevant but low-confidence memory won't drown out a moderately relevant, high-confidence, recent one. The result is a blended context string ready to feed into a prompt.

context = await store.retrieve("deployment")

Configuration

from memlife import MemoryConfig

config = MemoryConfig(
    db_path="./memlife.db",
    embedding_model="dummy",          # or "nomic-embed-text", etc.
    vector_backend="json",              # json | binary | sqlite_vec
    reflection_timeout=120.0,             # per-LLM-call timeout during reflection
    journal_decay_halflife_days=30.0,
    journal_decay_floor=0.15,
    namespace="default",
)

See MemoryConfig for the full set of options.

MCP Server

memlife ships with an MCP server so any MCP-compatible client can use it:

memlife-mcp-server --db-path ./memlife.db --vector-backend binary

Tools exposed include memory_store, memory_search, memory_search_journal, memory_search_episodes, memory_retrieve, memory_revise, memory_expire, memory_reflect, memory_gc, memory_vacuum, memory_store_triple, memory_search_triples, and memory_entity_neighbors.

Resources include memlife://stats, memlife://health, and memlife://contradictions.

Note: PyPI vs GitHub main — The --log-tool-calls flag ships in the current PyPI release (0.5.5 and later). If you are still on 0.5.4, omit --log-tool-calls from your MCP server command.

Windows / cloud-sync caveat: Do not point data_dir at a OneDrive, Dropbox, Google Drive, iCloud, or other cloud-sync folder, or at a network share. SQLite WAL mode keeps -wal and -shm sidecar files next to the database that are constantly rewritten; sync clients and real-time antivirus scanners can lock or corrupt those files, causing "database is locked" or checksum errors. Use a local, non-synced directory. If you still see locking errors on Windows, set sqlite_journal_mode="DELETE" in MemoryConfig to disable WAL mode.

What's new in 0.5.5

  • Version consistency. memlife.__version__ now matches pyproject.toml (0.5.5).
  • README accuracy. The MCP server tool list now lists only implemented tools and clarifies that memlife://contradictions is a resource, not a tool. The MemoryConfig example now uses real fields (reflection_timeout, journal_decay_halflife_days, journal_decay_floor).
  • Bounded tool-call logging cache. The in-memory dedup dict used by --log-tool-calls is capped so long-running MCP servers cannot grow it without bound.
  • Idempotent MCP shutdown. shutdown_mcp_server() now guards against double runs so SIGTERM and atexit cannot both attempt cleanup.
  • Windows namespace case-collision hardening. list_namespaces() now normalizes directory names to lowercase and warns/ignores mixed-case duplicates, preventing two directories that resolve to the same database from appearing as separate namespaces on case-insensitive filesystems.
  • Cloud-sync path warning. memlife now logs a warning when data_dir resolves under a known cloud-sync folder (OneDrive, Dropbox, Google Drive, iCloud, etc.) because sync clients can lock or corrupt SQLite WAL sidecar files.
  • Windows sqlite-vec fallback verified. The store already falls back from sqlite_vec to json gracefully when extension loading is unavailable. On Windows, where pysqlite3-binary is not installed, this fallback uses the stdlib sqlite3 module and continues to work.

What's new in 0.5.4

  • MCP reflection timeout controls. memlife-mcp-server now exposes --reflection-timeout and --reflection-total-timeout so reflection stays within the MCP client's window on slower LLM endpoints.
  • README tool list refreshed. The MCP server tool list now matches the actually implemented tools, including memory_recall, memory_revise, and memory_expire.
  • Memorias and polyphonic feature flags. --memorias-extraction and --polyphonic-recall can now be enabled from the MCP server CLI.
  • Episode search by tool name. memory_search_episodes now accepts a tool_name filter, making it easier to audit how a specific tool was used.
  • Vector backend default cleanup. --vector-backend now defaults to json explicitly, with clearer precedence when legacy flags are also supplied.

What's new in 0.5.3

  • Config validation regression fixed. MemoryConfig.validate() now runs all invariant checks even when vector_backend is left at its default auto value. Previously the default of None caused validation to return early, skipping decay and threshold checks.
  • Binary backend no longer loses JSON-stored vectors. BinaryVectorBackend deserialisation now falls back to JSON format for rows stored before the backend was switched, so changing from json to binary on an existing store does not make old vectors invisible to search.

What's new in 0.5.2

  • Binary vector backend is now used for retrieval. Previously the backend was selectable but recall still used inline cosine. Fact, episode, and journal vector recall now route through the configured backend, so binary uses Hamming distance search and sqlite_vec uses sqlite-vec KNN.
  • Vector backend precedence is centralised. MemoryConfig.resolved_vector_backend() gives a clear order: explicit vector_backend > use_binary_vectors > use_sqlite_vec > json default. Both legacy flags at once now resolve to binary with a warning, instead of silently depending on if/else order.
  • MCP server shutdown is clean. The shutdown path now closes the store, embedder, chat adapter, and reflector, and SIGTERM exits the process after cleanup instead of hanging.
  • Ollama adapters no longer create sessions before a loop exists. Sessions are created lazily on first async call, avoiding RuntimeError when the embedder/chat is instantiated outside an event loop.

What's new in 0.5.1

  • memlife-mcp-server now accepts --vector-backend {json,sqlite_vec,binary}.

  • Pluggable vector backends. Swap between JSON, binary, and sqlite-vec backends with a single config change. The new binary backend stores embeddings as bit-packed vectors for ~32x smaller storage, while sqlite-vec provides fast approximate nearest neighbours when the extension is available.

  • Public Metrics snapshot. store.metrics() returns a structured snapshot of counts, embedding coverage, reflection aggregates, recall counters, DB metadata, and schema migration health. Exposed via the memlife://stats MCP resource.

  • Schema migration status. store.migration_status() checks whether the database has all expected tables and columns, flags missing items, and reports SQLite version and page stats. Surfaces in Metrics so operators can detect a stale schema before it breaks.

  • pysqlite3 fallback for sqlite-vec. On interpreters whose stdlib sqlite3 is compiled without extension loading, memlife transparently falls back to pysqlite3 so sqlite-vec can still load.

  • Reflection audit trail. Reflection passes are recorded with proposed, kept, and dropped items, plus timing and model metadata. Paginated audit queries help debug what the reflection loop is actually doing.

  • Namespace default and validation hardening. The default namespace is now _default, names are validated/normalized before any DB is opened, and the config fails fast on unknown vector backends or unsafe paths.

License

MIT

Metadata

Release files for memlife 0.6.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for memlife 0.6.4
File Size Uploaded
memlife-0.6.4.tar.gz 140.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for memlife 0.6.4
File Interpreter ABI Platform
memlife-0.6.4-py3-none-any.whl Python 3 none any Details

Total release size: 262.3 kB

Release files / memlife-0.6.4.tar.gz

Download URL memlife-0.6.4.tar.gz
Size 140.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a65a6126c88f5c8207c114fc538cbee1957f511138376be33f2f10cb5dcd27e7
BLAKE2b-256 checksum
How to use checksums
ae6dc53c0f7cfb2eb9fa20f9a8760e13018876a7e11f7fcf9bb837060a4d342b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Release files / memlife-0.6.4-py3-none-any.whl

Download URL memlife-0.6.4-py3-none-any.whl
Size 121.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cdf4c77c74819f276b4257b5d12dc2d95954511fc37250fdf1ae64d903dbe9f3
BLAKE2b-256 checksum
How to use checksums
32a6536861e40a8dedbdeadb095519f899e85a7da153a0b34e37b4f51941e43c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page