Intelligent semantic memory with conflict detection, classification pipeline, and storage abstraction
Project description
casual-memory
Intelligent semantic memory with conflict detection, classification pipeline, and storage abstraction
๐ Features
๐ Classification Pipeline (Core Innovation)
- Protocol-based architecture - Composable, extensible classifiers
- NLI Pre-filtering - Fast semantic filtering (~50-200ms)
- LLM Conflict Detection - High-accuracy contradiction detection (96%+)
- LLM Duplicate Detection - Smart deduplication vs distinct facts
- Auto-Resolution - Confidence-based conflict resolution
- Graceful degradation - Heuristic fallback when LLM unavailable
๐ง Memory Intelligence
- Memory extraction from conversations (user & assistant messages)
- Conflict detection with categorization (location, preference, temporal, factual)
- Confidence scoring based on mention frequency and recency
- Memory archiving with soft-delete patterns
- Temporal memory with date normalization and expiry
๐ Storage Abstraction
- Protocol-based - Works with any vector database
- Optional adapters - Qdrant, PostgreSQL, Redis
- In-memory implementations - For testing
- Bring your own - Implement custom backends
๐ฆ Installation
Minimal (core only)
pip install casual-memory
With specific backends
# With NLI support (sentence-transformers)
pip install casual-memory[transformers]
# With Qdrant vector store
pip install casual-memory[qdrant]
# With PostgreSQL conflict store
pip install casual-memory[postgres]
# With Redis short-term store
pip install casual-memory[redis]
# Full installation (all extras)
pip install casual-memory[all]
CPU-only installation (no CUDA)
By default, PyTorch includes CUDA which is a large download. For CPU-only machines:
# Install CPU-only PyTorch first
pip install torch --index-url https://download.pytorch.org/whl/cpu
# Then install casual-memory with transformers
pip install casual-memory[transformers]
For development
git clone https://github.com/yourusername/casual-memory
cd casual-memory
uv sync --all-extras
๐ฏ Quick Start
Classification Pipeline
from casual_memory.classifiers import (
MemoryClassificationPipeline,
NLIClassifier,
ConflictClassifier,
DuplicateClassifier,
AutoResolutionClassifier,
SimilarMemory,
)
from casual_memory.intelligence import NLIPreFilter, LLMConflictVerifier, LLMDuplicateDetector
from casual_memory import MemoryFact
from casual_llm import create_provider, ModelConfig, Provider
# Initialize components
nli_filter = NLIPreFilter()
llm_provider = create_provider(ModelConfig(
name="qwen2.5:7b-instruct",
provider=Provider.OLLAMA,
base_url="http://localhost:11434"
))
conflict_verifier = LLMConflictVerifier(llm_provider, "qwen2.5:7b-instruct")
duplicate_detector = LLMDuplicateDetector(llm_provider, "qwen2.5:7b-instruct")
# Build pipeline
pipeline = MemoryClassificationPipeline(
classifiers=[
NLIClassifier(nli_filter=nli_filter),
ConflictClassifier(llm_conflict_verifier=conflict_verifier),
DuplicateClassifier(llm_duplicate_detector=duplicate_detector),
AutoResolutionClassifier(supersede_threshold=1.3, keep_threshold=0.7),
],
strategy="tiered", # "single", "tiered", or "all"
)
# Create new memory and similar memories from vector search
new_memory = MemoryFact(
text="I live in Paris",
type="fact",
tags=["location"],
importance=0.9,
confidence=0.8,
user_id="user_123",
)
similar_memories = [
SimilarMemory(
memory_id="mem_001",
memory=MemoryFact(
text="I live in London",
type="fact",
tags=["location"],
importance=0.8,
confidence=0.6,
user_id="user_123",
),
similarity_score=0.91,
)
]
# Classify the new memory against similar memories
result = await pipeline.classify(new_memory, similar_memories)
# Check overall outcome: "add", "skip", or "conflict"
print(f"Overall outcome: {result.overall_outcome}")
# Check individual similarity results
for sim_result in result.similarity_results:
print(f"Similar memory: {sim_result.similar_memory.memory.text}")
print(f"Outcome: {sim_result.outcome}") # "conflict", "superseded", "same", "neutral"
print(f"Classifier: {sim_result.classifier_name}")
if sim_result.outcome == "conflict":
print(f"Category: {sim_result.metadata.get('category')}")
Memory Extraction
from casual_memory.extractors import LLMMemoryExtracter
from casual_llm import UserMessage, AssistantMessage
# Create extractors for user and assistant memories
user_extractor = LLMMemoryExtracter(llm_provider=llm_provider, source="user")
assistant_extractor = LLMMemoryExtracter(llm_provider=llm_provider, source="assistant")
messages = [
UserMessage(content="My name is Alex and I live in Bangkok"),
AssistantMessage(content="Nice to meet you, Alex!"),
]
# Extract user-stated memories
user_memories = await user_extractor.extract(messages)
# [MemoryFact(text="My name is Alex", type="fact", importance=0.9, ...),
# MemoryFact(text="I live in Bangkok", type="fact", importance=0.8, ...)]
# Extract assistant-observed memories
assistant_memories = await assistant_extractor.extract(messages)
Custom Storage Backend
from typing import Any, Optional
class MyVectorStore:
"""Custom vector store implementing VectorMemoryStore protocol"""
def add(self, embedding: list[float], payload: dict) -> str:
"""Add memory to store, return memory ID."""
# Your implementation
return "memory_id"
def search(
self,
query_embedding: list[float],
top_k: int = 5,
min_score: float = 0.7,
filters: Optional[Any] = None,
) -> list[Any]:
"""Search for similar memories."""
# Your implementation
return []
def update_memory(self, memory_id: str, updates: dict) -> bool:
"""Update memory metadata (mention_count, last_seen, etc.)."""
# Your implementation
return True
def archive_memory(self, memory_id: str, superseded_by: Optional[str] = None) -> bool:
"""Soft-delete memory."""
# Your implementation
return True
๐๏ธ Architecture
Classification Pipeline Flow
Input: New Memory + Similar Memories (from vector search)
โ
1. NLI Classifier (~50-200ms)
โโ High entailment (โฅ0.85) โ same (duplicate)
โโ High neutral (โฅ0.5) โ neutral (distinct)
โโ Uncertain โ Pass to next classifier
โ
2. Conflict Classifier (~500-2000ms)
โโ LLM detects contradiction โ conflict
โโ No conflict โ Pass to next classifier
โ
3. Duplicate Classifier (~500-2000ms)
โโ Same fact โ same
โโ Refinement (more detail) โ superseded
โโ Distinct facts โ neutral
โ
4. Auto-Resolution Classifier
โโ Analyze conflict results
โโ High new confidence (ratio โฅ1.3) โ superseded (keep_new)
โโ High old confidence (ratio โค0.7) โ same (keep_old)
โโ Similar confidence โ Keep as conflict
โ
Output: MemoryClassificationResult
โโ overall_outcome: "add" | "skip" | "conflict"
โโ similarity_results: Individual outcomes for each similar memory
Key Concepts
Similarity Outcomes (for each similar memory):
conflict- Contradictory memories requiring user resolutionsuperseded- Similar memory should be archived (new one is better)same- Duplicate memory (update existing metadata)neutral- Distinct facts that can coexist
Memory Outcomes (overall action):
add- Insert new memory to vector storeskip- Update existing memory (increment mention_count)conflict- Create conflict record for user resolution
Confidence Scoring:
- Based on mention frequency (1 mention = 0.5, 5+ mentions = 0.95)
- Recency factor (decay after 30 days)
- Spread factor (boost if mentioned over time)
Memory Types:
fact- Factual information (name, location, job, etc.)preference- User preferences (likes, dislikes, habits)goal- User goals and aspirationsevent- Events (past or future)
๐ Documentation
- Architecture Guide - System design and concepts
- Examples - Working example code
๐งช Testing
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=casual_memory --cov-report=html
# Run specific test file
uv run pytest tests/classifiers/test_pipeline.py -v
# Run specific test
uv run pytest tests/classifiers/test_pipeline.py::test_pipeline_sequential_execution -v
๐ฏ Benchmarks
Classification pipeline performance on our test dataset:
| Model | Conflict Accuracy | Avg Time |
|---|---|---|
| qwen2.5:7b-instruct | 96.2% | 1.2s |
| llama3:8b | 94.5% | 1.5s |
| gpt-3.5-turbo | 97.1% | 0.8s |
NLI Pre-filter performance:
- Accuracy: 92.38% (SNLI), 90.04% (MNLI)
- Speed: ~200ms CPU, ~50ms GPU
- Filters: 70-85% of obvious cases before LLM
๐ค Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
๐ License
MIT License - see LICENSE for details.
๐ Acknowledgments
Built with:
- casual-llm - LLM provider abstraction
- sentence-transformers - NLI models
- Inspired by research in semantic memory and conflict detection
๐ Links
Project details
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 casual_memory-0.2.1.tar.gz.
File metadata
- Download URL: casual_memory-0.2.1.tar.gz
- Upload date:
- Size: 359.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 |
12b0b8613aabbdd1f3ed7359ef1f191458e898a8cf8f2edfead8ac039ab5f524
|
|
| MD5 |
14220d11783230a133c65f69d7624286
|
|
| BLAKE2b-256 |
b90bb93bf1ccdd36baf284d368cdbb6f12527128899ce06ec7367c7a23076a38
|
Provenance
The following attestation bundles were made for casual_memory-0.2.1.tar.gz:
Publisher:
release.yml on casualgenius/casual-memory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
casual_memory-0.2.1.tar.gz -
Subject digest:
12b0b8613aabbdd1f3ed7359ef1f191458e898a8cf8f2edfead8ac039ab5f524 - Sigstore transparency entry: 929911400
- Sigstore integration time:
-
Permalink:
casualgenius/casual-memory@b5b5dfa5f84cd425f18b75ef6ccebbc8af24256d -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/casualgenius
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b5b5dfa5f84cd425f18b75ef6ccebbc8af24256d -
Trigger Event:
push
-
Statement type:
File details
Details for the file casual_memory-0.2.1-py3-none-any.whl.
File metadata
- Download URL: casual_memory-0.2.1-py3-none-any.whl
- Upload date:
- Size: 75.6 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 |
09d5de0ff361505e64f7cbdcd5af4468099d42dd980117d238f41f7b69d38b50
|
|
| MD5 |
c6b97b74ad1a537a12ade9247277e102
|
|
| BLAKE2b-256 |
7738c31853b0c9f3152dd15da9aa709c0a72835855496b0c7b2d4d675c0b0513
|
Provenance
The following attestation bundles were made for casual_memory-0.2.1-py3-none-any.whl:
Publisher:
release.yml on casualgenius/casual-memory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
casual_memory-0.2.1-py3-none-any.whl -
Subject digest:
09d5de0ff361505e64f7cbdcd5af4468099d42dd980117d238f41f7b69d38b50 - Sigstore transparency entry: 929911404
- Sigstore integration time:
-
Permalink:
casualgenius/casual-memory@b5b5dfa5f84cd425f18b75ef6ccebbc8af24256d -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/casualgenius
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b5b5dfa5f84cd425f18b75ef6ccebbc8af24256d -
Trigger Event:
push
-
Statement type: