Skip to content

Memory System

MiMinions uses a three-tier memory architecture that automatically evolves across sessions without manual intervention. Two markdown tiers live inside each workspace; a global SQLite vector store carries reusable insights between workspaces.

Three-Tier Memory

Tier Storage Scope Description
1 memory/HISTORY.md Workspace-local One-line chronological log of each session
2 memory/MEMORY.md Workspace-local Stable extracted facts and decisions
3 ~/.miminions/global_memory.db Global Reusable cross-workspace insights (SQLite vector)

The markdown files (Tiers 1 and 2) live under <workspace_root>/memory/ and are the human-readable source of truth. The global SQLite database (Tier 3) is a vector index for cross-workspace recall.

The Memory Lifecycle

  [ContextBuilder]                            [Minion Agent]
  Reads Tier 2 & 3        System Prompt       Reasons & chats
  at startup     ────────────────────────►    with the user
                                                    │ (Chat ends)
  [Next Session]                                    ▼
  ContextBuilder         MD & SQLite        [MemoryDistiller]
  reads the newly  ◄──────────────────────   Uses llm_filter
  updated files          Upserts facts        to parse JSONL

Pre-Chat: ContextBuilder

Before the agent responds, ContextBuilder reads MEMORY.md (Tier 2) and queries the global SQLite database (Tier 3) for relevant insights, then injects both into the system prompt. The Tier-3 "Global Knowledge" section only appears when global_top_k > 0 and matching insights exist.

During Chat: Minion

The agent reasons with full memory context already injected. Raw conversation turns are appended to a sessions/*.jsonl log — nothing is written to memory during the session.

Post-Chat: MemoryDistiller + llm_filter

When the session ends (e.g., typing exit), MemoryDistiller sends the .jsonl transcript to the LLM via llm_filter, which extracts:

  • A brief session summary → appended to HISTORY.md (Tier 1)
  • New workspace facts → upserted into the Project Facts section of MEMORY.md (Tier 2)
  • Global insights → inserted (deduped) into global_memory.db (Tier 3)
from miminions.memory import MemoryDistiller, create_llm_filter
from miminions.agent.provider import ModelFactory

# Build an extraction filter backed by any pydantic_ai model
llm_filter = create_llm_filter(ModelFactory.create("openrouter"))

distiller = MemoryDistiller(llm_filter)  # global_db_path defaults to ~/.miminions/global_memory.db
result = distiller.distill_session(workspace, root_path, session_id="20260101T120000Z_ab12cd34")

print(result.history_summary)     # Tier-1 summary text
print(result.workspace_facts)     # Tier-2 facts
print(result.global_insights)     # Tier-3 insights actually stored
print(result.promoted_counts)     # {"tier1": 1, "tier2": 3, "tier3": 2}
print(result.dropped_reasons)     # human-readable notes on anything skipped

Failures are recorded, not raised

distill_session is resilient: an empty transcript, a failing llm_filter, or an unavailable Tier-3 store are all captured in result.dropped_reasons (e.g. "empty_session: ...", "llm_filter_error: ...", "tier3_unavailable: ...") rather than throwing. create_llm_filter likewise returns the raw model output (first 500 chars) as the summary if the model does not return valid JSON.

Storage Comparison

HISTORY.md (Tier 1) MEMORY.md (Tier 2) global_memory.db (Tier 3)
Contains Chronological session log Stable facts and decisions Cross-workspace insights
Updated by MemoryDistiller (append) MemoryDistiller (upsert) MemoryDistiller (deduped insert)
Read by agent No Yes Yes

Markdown Store Helpers

The two markdown tiers are powered by small, dependency-free helpers in md_store. They operate on a workspace root and create memory/MEMORY.md and memory/HISTORY.md from default templates on first use.

from miminions.memory import (
    append_history,
    read_memory,
    write_memory,
    upsert_memory_section,
)

append_history(root_path, "Set up CI and pinned dependencies")      # → Tier 1
upsert_memory_section(root_path, "Project Facts", ["Uses pytest", "Python 3.12"])  # → Tier 2
content = read_memory(root_path)   # full text of MEMORY.md
write_memory(root_path, content)   # replace MEMORY.md wholesale
Function Signature Purpose
append_history append_history(root_path, line) -> Path Append one bullet to HISTORY.md.
read_memory read_memory(root_path) -> str Read the full text of MEMORY.md.
write_memory write_memory(root_path, content) -> Path Replace the entire contents of MEMORY.md.
upsert_memory_section upsert_memory_section(root_path, heading, bullets) -> Path Insert or replace a ## {heading} section with bullet items.

These are the building blocks

MemoryDistiller calls append_history and upsert_memory_section for you. Reach for these helpers directly when you want to script the markdown tiers without an LLM in the loop. See Workspaces for the on-disk memory/ layout.

SQLite Vector Memory

For direct vector search, SQLiteMemory implements the abstract BaseMemory CRUD interface plus several search strategies, backed by sqlite-vec. Embeddings are generated locally with fastembed (ONNX runtime — no PyTorch or CUDA required), so you store and query plain text.

Import path and extra

SQLiteMemory is not re-exported from miminions.memory. Import it from the submodule, and install the sqlite extra:

pip install miminions[sqlite]
from miminions.memory.sqlite import SQLiteMemory   # ✅
# from miminions.memory import SQLiteMemory         # ✗ not exported

Quick Start

from miminions.memory.sqlite import SQLiteMemory

# Persistent on-disk store (creates parent dirs as needed)
memory = SQLiteMemory("memory.db")

entry_id = memory.create("Python is a programming language", metadata={"tag": "tech"})

results = memory.read("languages for coding", top_k=5)
for r in results:
    print(r["id"], r["distance"], r["text"], r["meta"])

memory.update(entry_id, "Python is a high-level programming language")
memory.delete(entry_id)
memory.close()

The constructor SQLiteMemory(db_path=None, model_name="all-MiniLM-L6-v2", dim=384) resolves storage by db_path:

SQLiteMemory("knowledge.db")   # stored at the given path
SQLiteMemory()                 # default location inside the package
SQLiteMemory(":memory:")       # in-process, lost when the process ends

model_name and dim must agree

The default model is all-MiniLM-L6-v2 (aliased to sentence-transformers/all-MiniLM-L6-v2), which produces 384-dimensional vectors. If you pass a different model_name, set dim to match its output or vector inserts will fail. The model downloads on first use.

Result shape

All search methods return a list of dicts. Every dict carries id, text, and meta (the parsed metadata — note the key is meta, not metadata). Some methods add extra keys:

Method Extra keys
read (vector KNN) distance
date_time_search created_at
hybrid_search source ("vector" or "keyword")

CRUD and search methods

# Exact and text matches
memory.get_by_id(entry_id)
memory.get_by_keyword("python", top_k=5)
memory.full_text_search("high level language", top_k=5)   # all words must appear
memory.regex_search(r"\bpython\b", top_k=5)               # IGNORECASE re.search

# Structured and time filters
memory.metadata_search("tag", "tech", top_k=5)            # json_extract on metadata
memory.date_time_search(start="2026-01-01", end="2026-06-21", top_k=5)

# Combined vector + keyword
memory.hybrid_search("coding languages", top_k=5)

# Bulk
memory.list_all()
memory.clear()
memory.close()
Method Description
create(text, metadata=None) -> str Embed and store text; returns a new uuid id.
read(query, top_k=5) -> list[dict] Vector KNN search; results include distance.
update(id, new_text) -> bool Re-embed and replace text; False if id missing.
delete(id) -> bool Remove an entry; True if a row was deleted.
get_by_id(id) -> dict \| None Fetch a single entry by id.
get_by_keyword(keyword, top_k=5) Case-insensitive LIKE %keyword% match.
full_text_search(query, top_k=5) Require every word of the query to appear.
metadata_search(key, value, top_k=5) Filter by a metadata key/value pair.
regex_search(pattern, top_k=5) Match text against a regex (IGNORECASE).
hybrid_search(query, top_k=5) Merge vector + keyword results, deduped, tagged with source.
date_time_search(start=None, end=None, top_k=5) Filter by created_at range; adds created_at.
list_all() -> list[dict] Return every entry (no limit).
clear() -> None Delete all entries.
close() Close the underlying SQLite connection.

Extension support

SQLiteMemory needs SQLite extension loading. The sqlite extra installs pysqlite3 for this; if your Python's sqlite3 lacks extension support, the constructor raises RuntimeError("SQLite extension loading not supported. Install pysqlite3: pip install pysqlite3").

Global memory path

The canonical cross-workspace database lives at ~/.miminions/global_memory.db. Resolve it programmatically with:

from miminions.memory.sqlite import get_global_memory_db_path

path = get_global_memory_db_path()   # creates ~/.miminions if needed

Attaching memory to an agent

A SQLiteMemory instance can be attached to a Minion so the agent gets memory CRUD and document-ingestion tools automatically, plus store_knowledge / recall_knowledge helpers:

from miminions.agent import create_minion
from miminions.memory.sqlite import SQLiteMemory

agent = create_minion("researcher", memory=SQLiteMemory(":memory:"))
agent.store_knowledge("The release ships on Friday", metadata={"topic": "release"})
hits = agent.recall_knowledge("when do we ship?", top_k=3)

When memory is attached, seven tools auto-register: memory_store, memory_recall, memory_update, memory_delete, memory_get, memory_list, and ingest_document. See Agent for the full integration.

See also

  • Context Builder — how memory tiers are composed into the system prompt.
  • Workspaces — the on-disk memory/ layout and workspace model.
  • Agent — attaching memory and the auto-registered memory tools.