AtomSpace-inspired memory + personality engine for AI agents
Project description
smrti
AtomSpace-inspired memory engine for AI agents. Stores beliefs as graph nodes with Bayesian truth values, emotional valence, and attention weights in a single SQLite file with vector indexing. No extra infra to maintain. Just Plug & Play.
Not just vector search. Embedding similarity is only an entry point — a fast index to seed graph traversal. What gets returned and why is governed by graph topology (typed relation edges), Bayesian truth values (PLN), attentional economics (STI/LTI), and emotional valence. Similarity is one signal among five, not the ranking.
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. A hybrid GLiNER2 + LLM pipeline auto-extracts entities and relation edges; the LLM is only called when ≥2 entities are found (~40–60% fewer LLM calls). Pronouns are resolved against the live graph using persisted entity context, not raw conversation history.
recall() — Embeds the query → KNN seeds (top-50) → 1-hop graph expansion → salience re-ranking: 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 is classified as critical_warning, known_antipattern, or context.
reflect() — Runs automatically every 60s (configurable via SMRTI_REFLECT_INTERVAL). Merges pending evidence via PLN, decays STI and confidence, propagates attention and valence to neighbors, heals orphaned episodes, promotes high-STI atoms to LTI, resolves contradictions, and prunes low-salience atoms. A 16-hyperparameter personality profile governs every weight and threshold.
Features
- Graph-structured memory — Concepts, beliefs, episodes, and goals as typed atoms with relation edges
- Bayesian truth maintenance — Probabilistic Logic Networks (PLN) for merging independent observations
- Personality-driven retrieval — 6 presets with 16 tunable hyperparameters that shape what gets surfaced
- Multi-tenant isolation — Tenant/space overlay model with cross-space reads and single-space writes
- Three server modes — MCP (stdio), REST API, and OpenAI-compatible proxy
- Automatic entity extraction — all server modes build concept nodes and relation edges from stored episodes automatically; cross-session coreference resolution grounds pronouns against the live memory graph (on by default; set
SMRTI_EXTRACT_MODELand optionallySMRTI_EXTRACT_URLto configure) - Entity resolution — 5-tier cascade: exact match, cross-type label match, alias lookup, fuzzy (RapidFuzz), embedding similarity
- Memory visualizer — Built-in graph explorer (
smrti serve viz) to inspect atoms, relations, and attention weights in the browser; includes an LLM Calls debug tab showing every extraction request with full request/response, timing, and recalled memories
- Zero external services — Single SQLite file with sqlite-vec for KNN search, ONNX embeddings on CPU
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
# Initialize a database
smrti init --db ~/.smrti/memory.db --personality balanced
# Check status
smrti status
# Start servers
smrti serve mcp # MCP stdio server (for Claude, etc.)
smrti serve rest # FastAPI on :8420
smrti serve viz # FastAPI on :8420 + opens memory visualizer in browser (see screenshot above)
smrti serve proxy # OpenAI-compatible proxy on :8421
Server Modes
MCP Server
Exposes 8 tools over stdio for direct LLM integration (Claude, etc.):
| Tool | Description |
|---|---|
remember |
Store an episode, goal, or belief (use type=belief + evidence to assert a probabilistic fact) |
recall |
Semantic search with salience scoring |
reflect |
Run a consolidation epoch |
forget |
Lower confidence on a memory |
status |
Get memory statistics and list of all spaces for the tenant |
personality |
Get or set personality preset |
space_query |
Query two spaces: op=overlap (Jaccard), op=intersection, op=diff |
space_merge |
Materialize a bridge space from the overlap between two spaces |
smrti serve mcp
Configure via environment variables:
export SMRTI_DB=~/.smrti/memory.db
export SMRTI_PERSONALITY=balanced
export SMRTI_TENANT_ID=default
export SMRTI_SPACE=default
export SMRTI_READ_SPACES=default,shared # comma-separated
export SMRTI_REFLECT_INTERVAL=60 # auto-consolidation interval in seconds (0 to disable)
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
Drop-in replacement for https://api.openai.com/v1/chat/completions. Intercepts requests, 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
Use it from any OpenAI-compatible client:
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",
}
)
The proxy automatically:
- Recalls relevant memories from the specified read spaces (using recent conversation context, not just the last message)
- Classifies each memory by severity and injects them as two distinct sections: behavioral constraints (
YOU MUST NOT/AVOID) forcritical_warningandknown_antipatternmemories, and background context (Note:) for neutral memories — each with its own preamble and confidence qualifier - Stores the most recent user message and the assistant response as episodes
- Calls the LLM to extract entities and claims, creates concept nodes, and links them to the episode with typed relation edges (on by default; disable with
SMRTI_EXTRACT=0)
Configure with:
export SMRTI_UPSTREAM_URL=https://api.openai.com # or any OpenAI-compatible API
export SMRTI_RECALL_TOP_K=5
export SMRTI_RECALL_MIN_CONFIDENCE=0.3
export SMRTI_QUERY_MODE=concat # "concat" (default) or "last" for last-message-only
export SMRTI_QUERY_CONTEXT_MSGS=5 # number of recent messages to include in query
export SMRTI_QUERY_MAX_CHARS=500 # max characters for the recall query
export SMRTI_REFLECT_INTERVAL=60 # auto-consolidation interval in seconds (0 to disable)
export SMRTI_EXTRACT=1 # enable entity/claim extraction (default: on)
export SMRTI_EXTRACT_MODE=hybrid # hybrid (default), llm (LLM-only), local (no LLM)
export SMRTI_EXTRACT_URL= # LLM endpoint for extraction (defaults to SMRTI_UPSTREAM_URL)
export SMRTI_EXTRACT_MODEL= # model for extraction calls (proxy defaults to request model)
export SMRTI_EXTRACT_THINKING=disabled # disabled (default), auto, enabled — extraction works better
# with thinking off (faster, avoids token-budget exhaustion on
# Qwen3/DeepSeek-R1); set to "auto" to let the model decide
export SMRTI_EXTRACT_TIMEOUT=60 # LLM extraction request timeout in seconds (default: 60)
export SMRTI_NER_MODEL= # GLiNER2 model for local NER (default: fastino/gliner2-multi-v1)
Ignoring Automated Messages
Agentic frameworks often produce periodic system messages (heartbeat checks, status pings, tool scaffolding) that should not pollute memory. Set SMRTI_IGNORE_PATTERNS to a newline-separated list of regex patterns; any remember() call whose content matches is silently dropped before embedding or extraction runs.
# Ignore picoclaw heartbeat prompts and responses
export SMRTI_IGNORE_PATTERNS="^# Heartbeat Check
^HEARTBEAT_OK$"
Patterns are matched with re.search (anchors optional). The variable applies to all server modes (MCP, REST, proxy).
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, each with its own personality and consolidation cycle. read_spaces is the social graph.
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: beliefs decay at different rates, emotional weights diverge, and what surfaces during recall depends on personality.
Some 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. Role-based perspectives for the same user across different contexts.
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
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
Salience formula:
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 w_sti to w_val so critical errors outrank recent trivia.
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]
Testing
pytest tests/ -v
License
MIT
Project details
Release history Release notifications | RSS feed
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.6.12.tar.gz.
File metadata
- Download URL: smrti-0.6.12.tar.gz
- Upload date:
- Size: 81.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41a3af5cf4719c8c0ffb7ce06b0461caa89bfa1df495c5dcc68027b16edbb386
|
|
| MD5 |
e566bba76b4fa8fd9131819b9ca3c199
|
|
| BLAKE2b-256 |
97e35b4c44944adea9f6a29eba2f277648beb7615830050082def1fb1eadc5ec
|
Provenance
The following attestation bundles were made for smrti-0.6.12.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.6.12.tar.gz -
Subject digest:
41a3af5cf4719c8c0ffb7ce06b0461caa89bfa1df495c5dcc68027b16edbb386 - Sigstore transparency entry: 1154537215
- Sigstore integration time:
-
Permalink:
cyqlelabs/smrti@cd4aded782e7175a1350c301a6be1edeeb61dc39 -
Branch / Tag:
refs/tags/v0.6.12 - Owner: https://github.com/cyqlelabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cd4aded782e7175a1350c301a6be1edeeb61dc39 -
Trigger Event:
push
-
Statement type:
File details
Details for the file smrti-0.6.12-py3-none-any.whl.
File metadata
- Download URL: smrti-0.6.12-py3-none-any.whl
- Upload date:
- Size: 95.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03077d235d81046bc462a5b702348b6a0fe7655452e17aea243c92a782793636
|
|
| MD5 |
fc2200d7e5208a697dd93d4655267223
|
|
| BLAKE2b-256 |
b4246e4da4ea533fa9016ea7e3a69fe2821ac02f54e1296f6dadaa47ee7ecdb2
|
Provenance
The following attestation bundles were made for smrti-0.6.12-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.6.12-py3-none-any.whl -
Subject digest:
03077d235d81046bc462a5b702348b6a0fe7655452e17aea243c92a782793636 - Sigstore transparency entry: 1154537217
- Sigstore integration time:
-
Permalink:
cyqlelabs/smrti@cd4aded782e7175a1350c301a6be1edeeb61dc39 -
Branch / Tag:
refs/tags/v0.6.12 - Owner: https://github.com/cyqlelabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cd4aded782e7175a1350c301a6be1edeeb61dc39 -
Trigger Event:
push
-
Statement type: