Tramontane's 3-tier agent-controlled memory with Mistral embeddings.
Without memory, every agent call starts from zero. The agent forgets the user's name, past decisions, what worked before. TramontaneMemory gives agents persistent, searchable, GDPR-compliant memory.
Tier 1: Working Memory — Always in context, agent-editable labels
Tier 2: Factual Memory — Knowledge graph + embeddings + FTS5
Tier 3: Experiential — Outcomes, learnings, self-improvement
from tramontane import Agent, TramontaneMemory
memory = TramontaneMemory(db_path="memory.db")
agent = Agent(
role="Gerald",
goal="Remember everything about clients",
backstory="Autonomous business agent",
tramontane_memory=memory,
memory_tools=True,
auto_extract_facts=True,
working_memory_blocks=["Goals", "User"],
)Small, labeled blocks always injected into the system prompt. The agent reads and edits them.
memory.set_working_block("gerald", "Goals", "Close 3 deals this week")
memory.set_working_block("gerald", "User", "Name: Alice, Pref: warm colors")
blocks = memory.get_working_blocks("gerald")
# [{"label": "Goals", "content": "Close 3 deals..."}, ...]Working memory is injected as:
## Working Memory
### Goals
Close 3 deals this week
### User
Name: Alice, Pref: warm colors
Stores atomic facts with embeddings for semantic search.
# Store a fact
mem_id = await memory.retain(
content="Acme Corp prefers React for frontend",
entity="Acme Corp",
category="preference",
source="meeting_notes",
)
# Recall relevant facts
results = await memory.recall("What does Acme prefer?", top_k=5)
# Update a fact
await memory.update(mem_id, "Acme Corp now prefers Vue.js")
# Forget (GDPR Article 17)
await memory.forget(mem_id, reason="GDPR erasure request")When storing a fact, the system embeds it and compares against existing facts. If cosine similarity > 0.92, it updates the existing fact instead of creating a duplicate.
With auto_extract_facts=True, after every agent.run(), the output is sent to ministral-3b which extracts atomic facts:
# Agent outputs: "Acme Corp is based in Paris. They have 50 employees."
# Auto-extracted:
# - "Acme Corp is based in Paris" (entity: Acme Corp, category: fact)
# - "Acme Corp has 50 employees" (entity: Acme Corp, category: fact)Cost: EUR 0.04/1M tokens (ministral-3b).
Records what the agent did, what happened, and what it learned.
await memory.record_experience(
action_type="lead_qualification",
summary="Qualified Acme Corp as hot lead",
outcome="Client converted within 2 weeks",
score=0.95,
agent_role="Qualifier",
model="ministral-3b",
cost=0.001,
)When you call memory.recall(query), four channels run simultaneously:
- Semantic: Embed query with mistral-embed, cosine similarity against all fact embeddings
- Keyword: FTS5 BM25 full-text search on fact content
- Entity: Extract entities from query (capitalized words, quoted phrases), traverse entity graph 1-2 hops
- Temporal: Score by recency, access frequency, and confidence
Results are fused using Reciprocal Rank Fusion (RRF, k=60):
rrf_score(doc) = sum(1 / (60 + rank_in_channel)) for each channel
When memory_tools=True, the agent gets 5 callable tools:
| Tool | What it does |
|---|---|
retain_memory(content, entity, category) |
Store a new fact |
recall_memory(query, top_k) |
Search memory |
reflect_on_memory(question) |
Synthesize insights from multiple memories |
forget_memory(memory_id, reason) |
GDPR-safe deletion |
update_memory(memory_id, new_content) |
Correct existing fact |
The agent decides when to call these during execution.
reflect() uses mistral-small to synthesize insights from multiple recalled memories:
answer = await memory.reflect("What patterns have I seen in successful deals?")
# Returns synthesized answer drawing from experiential + factual memoryforget()soft-deletes (setserased_at, removes from FTS5 index)- Every erasure logged to
memory_erasure_logtable - All operations logged to
memory_audittable - Stats available via
memory.stats()
class TramontaneMemory:
async def retain(content, entity="", category="fact", source="") -> str
async def recall(query, top_k=5, recency_weight=0.3) -> list[dict]
async def reflect(question) -> str
async def forget(memory_id, reason="") -> bool
async def update(memory_id, new_content) -> bool
async def extract_facts(text, source="") -> list[str]
async def record_experience(action_type, summary, outcome, score, ...) -> str
def get_working_blocks(agent_id) -> list[dict]
def set_working_block(agent_id, label, content) -> None
def format_context(results, max_tokens=2000) -> str
def stats() -> MemoryStats
fact_count: int # property
experience_count: int # property| Operation | Model | Cost |
|---|---|---|
| Embedding (retain/recall) | mistral-embed | EUR 0.10/1M tokens |
| Fact extraction | ministral-3b | EUR 0.04/1M tokens |
| Reflection | mistral-small | EUR 0.10/1M tokens |
| Keyword/entity/temporal search | SQLite | Free |