smrti
Long-term memory for AI agents in a single SQLite file. Your agent remembers what matters, forgets what doesn't, and never repeats a critical mistake — no vector database, no external services, no infrastructure.
Inspired by AtomSpace: memories are graph nodes with Bayesian truth values, emotional valence, and attention weights. Embedding similarity is only the entry point — a fast index to seed graph traversal. What gets recalled and why is governed by graph topology, probabilistic truth maintenance (PLN), attentional economics (STI/LTI), and emotional valence. Similarity is one signal among five, not the ranking.
- Install
- Quick Start
- Use with Claude / MCP clients
- How It Works
- Server Modes
- Configuration Reference
- Security & Monitoring
- Multi-Tenant / Space Model
- Personality System
- Architecture
Why smrti
- Zero infrastructure — one SQLite file with sqlite-vec for KNN and ONNX embeddings on CPU.
pip installand go. - Error-avoidance memory — severe failures get a long-term-importance floor so they survive pruning, and recall dynamically boosts them: old-but-critical errors outrank recent trivia. Recalled memories are classified as
critical_warning,known_antipattern, orcontext. - Automatic knowledge graph — a hybrid GLiNER2 + LLM pipeline extracts entities and typed relations from everything you store, and resolves pronouns against the persisted graph — no manual schema.
- Three integration paths — MCP server for Claude and other LLM clients, REST API, or an OpenAI-compatible proxy that adds memory to any existing app by changing one base URL.
- Multilingual — 50+ languages end-to-end (multilingual embeddings, zero-shot NER, language-agnostic sentiment). No English-only heuristics anywhere.
- Personality-driven — six presets (16 tunable hyperparameters) shape what each agent notices, retains, and forgets. The same history produces different memories in different agents.
Install
pip install smrti
Quick Start
Python API
from smrti import Smrti
mem = Smrti(db_path="~/.smrti/memory.db", personality="balanced")
# Store memories
mem.remember("Alice prefers TypeScript", probability=0.9, valence=0.3)
mem.remember("The deploy pipeline is broken", probability=0.95, valence=-0.7)
# Recall by semantic similarity + salience
results = mem.recall("programming languages")
for r in results:
print(f"{r.atom.label} (salience={r.salience:.2f}, confidence={r.atom.truth.confidence:.2f})")
# Assert a belief with evidence
mem.believe("Python is the best language for ML", probability=0.85, evidence="Team survey results")
# Consolidate: decay, promote, prune, resolve contradictions
epoch = mem.reflect()
print(f"Updated {epoch.beliefs_updated} beliefs, pruned {epoch.atoms_pruned} atoms")
mem.close()
CLI
smrti init --db ~/.smrti/memory.db --personality balanced # create a database
smrti status # inspect it
smrti serve mcp # MCP stdio server (Claude, etc.)
smrti serve rest # REST API on :8420
smrti serve viz # REST API + memory visualizer in the browser
smrti serve proxy # OpenAI-compatible proxy on :8421
smrti serve town # town-life simulation demo on :8430
How It Works
remember() — Embeds and stores text as a typed atom (concept, belief, episode, or goal) with a Bayesian truth value, attention weight, and valence score. Evidence is append-only; truth values update via PLN revision. Entities and relation edges are extracted automatically (the LLM is only called when GLiNER finds ≥2 entities, cutting LLM calls ~40–60%).
recall() — Embeds the query → KNN seeds → 1-hop graph expansion → salience re-ranking:
S = w_sim × similarity + w_sti × sti + w_conf × confidence + w_lti × lti + w_val × |valence| × intensity
When valence < −0.5, weight shifts dynamically from STI to valence so critical errors outrank recent trivia. Each result carries a severity classification (critical_warning, known_antipattern, or context).
reflect() — Runs automatically every 60 s (SMRTI_REFLECT_INTERVAL). Merges pending evidence via PLN, decays attention and confidence, propagates both to neighbors, heals orphaned episodes, promotes high-STI atoms to long-term importance, resolves contradictions, and prunes low-salience atoms. The personality profile governs every weight and threshold.
Server Modes
MCP Server
Exposes 8 tools over stdio for direct LLM integration.
Claude Code:
claude mcp add smrti -- smrti serve mcp
Claude Desktop (or any MCP client) — add to your MCP config:
{
"mcpServers": {
"smrti": {
"command": "smrti",
"args": ["serve", "mcp"],
"env": { "SMRTI_DB": "~/.smrti/memory.db" }
}
}
}
| Tool | Description |
|---|---|
remember |
Store an episode, goal, or belief (use type=belief + evidence to assert a probabilistic fact) |
recall |
Semantic search with salience scoring and severity classification |
reflect |
Run a consolidation epoch |
forget |
Lower confidence on a memory |
status |
Memory statistics and the tenant's spaces |
personality |
Get or set the personality preset |
space_query |
Compare two spaces: op=overlap (Jaccard), op=intersection, op=diff |
space_merge |
Materialize a bridge space from the overlap between two spaces |
REST API
Full CRUD over HTTP on port 8420:
smrti serve rest --host 0.0.0.0 --port 8420
# Store a memory
curl -X POST http://localhost:8420/remember \
-H "Content-Type: application/json" \
-d '{"content": "Alice prefers TypeScript", "probability": 0.9}'
# Recall
curl -X POST http://localhost:8420/recall \
-d '{"query": "programming languages", "top_k": 5}'
# Run consolidation
curl -X POST http://localhost:8420/reflect
# Get status
curl http://localhost:8420/status
OpenAI-Compatible Proxy
The fastest way to add memory to an existing app: point your OpenAI client at the proxy and keep everything else the same. It intercepts each chat request, injects relevant memories into the system prompt, and stores the exchange afterward.
smrti serve proxy --host 0.0.0.0 --port 8421 --upstream https://api.openai.com
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8421/v1",
api_key="sk-..." # forwarded to upstream
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "What do you know about Alice?"}],
extra_headers={
"X-Smrti-Tenant-Id": "user_123",
"X-Smrti-Write-Space": "work",
"X-Smrti-Read-Spaces": "work,personal",
}
)
On every request the proxy:
- Recalls relevant memories from the read spaces, building the query from recent conversation context (not just the last message)
- Injects them into the system prompt in two sections — behavioral constraints (
YOU MUST NOT/AVOID) forcritical_warningandknown_antipatternmemories, and background context (Note:) for the rest — each with a confidence qualifier - Stores the user message and assistant response as episodes (identical episodes are deduplicated per tenant/space)
- Extracts entities and claims into concept nodes and typed relation edges
Works with any OpenAI-compatible upstream — including local llama.cpp, vLLM, or Ollama endpoints.
Memory Visualizer
smrti serve viz opens a browser-based graph explorer to inspect atoms, relations, and attention weights, plus an LLM Calls debug tab showing every extraction request with full request/response, timing, and recalled memories.
Configuration Reference
All server modes read the same environment variables. Everything works with zero configuration; set these to customize.
Core (all modes):
| Variable | Default | Purpose |
|---|---|---|
SMRTI_DB |
~/.smrti/memory.db |
Database file path |
SMRTI_PERSONALITY |
balanced |
Personality preset |
SMRTI_TENANT_ID |
default |
Tenant partition (hard isolation) |
SMRTI_SPACE |
default |
Write space |
SMRTI_READ_SPACES |
write space | Comma-separated spaces to read from |
SMRTI_REFLECT_INTERVAL |
60 |
Auto-consolidation interval in seconds (0 = off) |
SMRTI_IGNORE_PATTERNS |
— | Newline-separated regexes; matching content is dropped before storage (see below) |
Security (REST / proxy / viz):
| Variable | Default | Purpose |
|---|---|---|
SMRTI_API_KEY |
— | When set, every request must send Authorization: Bearer <key> or X-Api-Key: <key> |
SMRTI_CORS_ORIGINS |
— | Comma-separated allowed origins for the proxy; CORS middleware is only added when set |
Proxy:
| Variable | Default | Purpose |
|---|---|---|
SMRTI_UPSTREAM_URL |
https://api.openai.com |
Upstream OpenAI-compatible API |
SMRTI_RECALL_TOP_K |
5 |
Memories to inject per request |
SMRTI_RECALL_MIN_CONFIDENCE |
0.3 |
Confidence floor for injected memories |
SMRTI_QUERY_MODE |
concat |
Recall query source: concat recent context or last message only |
SMRTI_QUERY_CONTEXT_MSGS |
5 |
Recent messages included in the recall query |
SMRTI_QUERY_MAX_CHARS |
500 |
Max characters of the recall query |
SMRTI_INJECT_MAX_CHARS |
500 |
Max characters per injected memory |
Extraction (all modes):
| Variable | Default | Purpose |
|---|---|---|
SMRTI_EXTRACT |
1 |
Entity/claim extraction after every remember (0 = off) |
SMRTI_EXTRACT_MODE |
hybrid |
hybrid (GLiNER + LLM), llm (LLM-only), local (no LLM) |
SMRTI_EXTRACT_URL |
upstream URL | LLM endpoint for extraction calls |
SMRTI_EXTRACT_MODEL |
request model | Model for extraction calls |
SMRTI_EXTRACT_THINKING |
disabled |
Chain-of-thought for extraction: disabled is faster and avoids token-budget exhaustion on thinking models (Qwen3, DeepSeek-R1); also auto, enabled |
SMRTI_EXTRACT_TIMEOUT |
60 |
Extraction request timeout in seconds |
SMRTI_NER_MODEL |
fastino/gliner2-multi-v1 |
GLiNER2 model for local zero-shot NER |
Ignoring Automated Messages
Agentic frameworks often produce periodic system messages (heartbeat checks, status pings, tool scaffolding) that should not pollute memory. Any remember() call matching SMRTI_IGNORE_PATTERNS is silently dropped before embedding or extraction:
export SMRTI_IGNORE_PATTERNS="^# Heartbeat Check
^HEARTBEAT_OK$"
Patterns are matched with re.search (anchors optional) and apply to all server modes.
Security & Monitoring
API key auth — HTTP servers are open by default for local use. Set SMRTI_API_KEY to require a key on every REST, proxy, and visualizer request (Authorization: Bearer <key> or X-Api-Key: <key>). The CLI warns when you bind to a non-loopback host without a key set.
Prometheus metrics — REST and proxy expose GET /metrics in Prometheus text format, with zero extra dependencies. Gauges include smrti_atoms_total, smrti_atoms_by_type, smrti_epoch_count, and the active personality hyperparameters, all labeled by tenant and space — so you can alert per tenant (e.g. "atom count flatlined → remember is failing") and track personality drift from Grafana or any Prometheus-compatible system.
Multi-Tenant / Space Model
Tenants are hard walls: atoms, embeddings, and attention weights never cross them. Spaces are permeable layers within a tenant: you write to one, read from many, and each has its own personality and consolidation cycle.
researcher = Smrti(tenant_id="team", write_space="researcher",
read_spaces=["researcher", "shared"], personality="curious")
deployer = Smrti(tenant_id="team", write_space="deployer",
read_spaces=["deployer", "shared"], personality="deterministic")
coordinator = Smrti(tenant_id="team", write_space="coordinator",
read_spaces=["coordinator", "shared", "researcher", "deployer"],
personality="analytical")
shared = Smrti(tenant_id="team", write_space="shared")
Each space consolidates independently. The researcher forgets fast; the deployer holds onto critical failures; the coordinator sees everything but filters through its own lens. Over time each agent develops a different understanding of the same shared history.
Things people build with this: agent teams with private working memory and shared project context, multi-agent simulations where each agent remembers the same event differently, and role-based perspectives for the same user across contexts.
Spaces also support set-theory operations — overlap, intersection, difference, union, symmetric difference — and can materialize bridge spaces from the overlap between two spaces (see the space_query and space_merge tools).
Personality System
Six built-in presets control retrieval behavior, decay rates, and emotional dynamics:
| Preset | Bias | Use Case |
|---|---|---|
balanced |
Equal weights across all signals | General-purpose agents |
analytical |
High confidence weight, low valence | Logical reasoning, data-driven decisions |
curious |
High STI weight, fast decay | Exploration, novelty-seeking |
empathetic |
High valence weight, emotional propagation | Relationship-focused agents |
maverick |
Slow decay, high propagation | Independent, contrarian reasoning |
deterministic |
Fast learning, slow decay, laser focus | Agentic workflows, code gen, deployments |
Each preset tunes 16 hyperparameters. To create a custom personality, start from a preset and override individual values via the personality DB table or the /personality API endpoint.
Hyperparameter reference (16 parameters, defaults from the balanced preset)
Salience weights — control how retrieval ranks results (should sum to ~1.0):
| Parameter | Default | Effect |
|---|---|---|
w_similarity |
0.35 | Weight of embedding cosine similarity |
w_sti |
0.25 | Weight of short-term importance (recency/access) |
w_confidence |
0.20 | Weight of truth value confidence |
w_lti |
0.10 | Weight of long-term importance |
w_valence |
0.10 | Weight of emotional intensity (dynamically boosted when valence < -0.5) |
Belief dynamics — govern how confidence evolves over time:
| Parameter | Default | Effect |
|---|---|---|
confidence_decay_rate |
0.02 | Per-epoch confidence decay. Higher = memories fade faster |
confidence_update_lr |
0.3 | Learning rate for PLN evidence merges. Higher = new evidence has more impact |
min_confidence_to_surface |
0.1 | Floor below which atoms are excluded from recall results |
Attention dynamics — control what stays in focus:
| Parameter | Default | Effect |
|---|---|---|
sti_decay_rate |
0.1 | Per-epoch STI decay. Higher = faster attention loss |
sti_boost_on_access |
0.5 | STI added each time an atom is recalled. Higher = stronger recency bias |
sti_propagation_factor |
0.15 | Fraction of STI boost propagated to linked atoms. Higher = broader activation |
lti_promotion_threshold |
0.7 | Cumulative STI required to increment LTI. Higher = harder to become permanent |
Emotional dynamics — shape how valence influences behavior:
| Parameter | Default | Effect |
|---|---|---|
valence_weight |
0.2 | Global scaling factor for emotional influence on salience |
valence_propagation |
0.1 | Fraction of valence propagated to linked atoms during epochs |
mood_inertia |
0.8 | Resistance to mood shifts (0 = reactive, 1 = stable) |
Architecture
graph TD
subgraph Facade
S["Smrti<br/><small>remember · recall · believe · reflect · forget · status</small>"]
end
subgraph Servers
MCP["mcp.py<br/><small>MCP stdio</small>"]
REST["rest.py<br/><small>FastAPI :8420</small>"]
PROXY["proxy.py<br/><small>OpenAI proxy :8421</small>"]
end
subgraph Core
AS["AtomSpace"]
DB["Database"]
EMB["Embedder"]
MOD["Models"]
end
subgraph Retrieval
FAN["fan_out"]
SAL["salience"]
CLS["classify"]
end
subgraph Evolution
EPO["epoch"]
TRU["truth"]
CON["connections"]
HEA["healing"]
end
subgraph Spaces
SOP["set_ops"]
EMG["emergence"]
end
subgraph Extraction
EXT["extract"]
RES["resolve"]
ALI["aliases"]
end
subgraph Storage
SQL["SQLite + sqlite-vec<br/><small>multilingual-MiniLM-L12-v2 · 384d · ONNX CPU</small>"]
end
MCP & REST & PROXY --> S
S --> Core & Retrieval & Evolution & Extraction & Spaces
Core & Retrieval & Evolution & Extraction & Spaces --> SQL
Retrieval pipeline: Embed query → KNN over tenant partition → filter to read spaces → 1-hop graph expansion → salience scoring → top-k
Consolidation epoch (runs automatically every SMRTI_REFLECT_INTERVAL seconds, or manually via reflect()):
- Process pending evidence via Bayesian update
- Decay STI and confidence
- Propagate STI and valence to 1-hop neighbors
- Heal orphaned episodes (link to most salient person)
- Promote high-STI atoms to LTI
- Resolve contradictions (weaken less confident belief)
- Discover cross-domain connections (every 10th epoch)
- Materialize cross-space bridge atoms (every 10th epoch)
- Prune atoms below confidence/LTI floors
Data Model
| Atom Type | Purpose | Example |
|---|---|---|
concept |
Reusable entities | "Alice", "Python", "OpenAI" |
belief |
Probabilistic facts | "Alice prefers TypeScript" |
episode |
Timestamped observations | "User asked about deployment" |
goal |
Desired states | "Finish the migration by Friday" |
relation |
Edges between atoms | Alice → works_at → Acme Corp |
Each atom carries:
- TruthValue —
probability[0,1] andconfidence[0,1], merged via PLN revision - AttentionValue —
sti(short-term importance, decays fast) andlti(long-term, accumulates) - Valence — emotional tone [-1,1] and intensity [0,1]
smrti-town
A living demo: smrti-town is a town-life simulation where agents with persistent smrti memory live, talk, form relationships, and die in a Phaser 3 canvas. Each agent's behavior is driven by what it remembers — and how its personality shapes those memories.
smrti serve town # simulation + frontend on :8430
Testing
pytest tests/ -v
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smrti-0.8.0.tar.gz.
File metadata
- Download URL: smrti-0.8.0.tar.gz
- Upload date:
- Size: 99.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2c30ef4bb53c5df4a9b20ee1540aedc035af07a72e8d05ab473bef08f58d309
|
|
| MD5 |
7d9894a0d1c11bda8aa5cf381678e5a7
|
|
| BLAKE2b-256 |
787efd5e70239207aeb091e1ce11234e9c2a8aee3c97e5a80260256fcc26c608
|
Provenance
The following attestation bundles were made for smrti-0.8.0.tar.gz:
Publisher:
publish.yml on cyqlelabs/smrti
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smrti-0.8.0.tar.gz -
Subject digest:
e2c30ef4bb53c5df4a9b20ee1540aedc035af07a72e8d05ab473bef08f58d309 - Sigstore transparency entry: 2439152267
- Sigstore integration time:
-
Permalink:
cyqlelabs/smrti@917ed7dccc1827bfd1f11d5f26513721cb74ab43 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/cyqlelabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@917ed7dccc1827bfd1f11d5f26513721cb74ab43 -
Trigger Event:
push
-
Statement type:
File details
Details for the file smrti-0.8.0-py3-none-any.whl.
File metadata
- Download URL: smrti-0.8.0-py3-none-any.whl
- Upload date:
- Size: 115.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a23444a4f6eca113b7b855bff6db375fd0ceeff01c3727dec586bdb217e8b05e
|
|
| MD5 |
e56ba244e7af01f19b46032de9dbf413
|
|
| BLAKE2b-256 |
47ab0634d83e3fdc0ea7324d9deac3367ee188a24d006b39933352fce82883fb
|
Provenance
The following attestation bundles were made for smrti-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on cyqlelabs/smrti
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smrti-0.8.0-py3-none-any.whl -
Subject digest:
a23444a4f6eca113b7b855bff6db375fd0ceeff01c3727dec586bdb217e8b05e - Sigstore transparency entry: 2439152287
- Sigstore integration time:
-
Permalink:
cyqlelabs/smrti@917ed7dccc1827bfd1f11d5f26513721cb74ab43 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/cyqlelabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@917ed7dccc1827bfd1f11d5f26513721cb74ab43 -
Trigger Event:
push
-
Statement type: