Intelligent semantic memory with conflict detection, classification pipeline, and storage abstraction
- 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 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
- Protocol-based - Works with any vector database
- Optional adapters - Qdrant, PostgreSQL, Redis
- In-memory implementations - For testing
- Bring your own - Implement custom backends
pip install casual-memory# 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]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]git clone https://github.com/yourusername/casual-memory
cd casual-memory
uv sync --all-extrasfrom 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_client, create_model, ClientConfig, ModelConfig, Provider
# Initialize components
nli_filter = NLIPreFilter()
client = create_client(ClientConfig(
provider=Provider.OLLAMA,
base_url="http://localhost:11434"
))
model = create_model(client, ModelConfig(name="qwen2.5:7b-instruct"))
conflict_verifier = LLMConflictVerifier(model)
duplicate_detector = LLMDuplicateDetector(model)
# 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,
entity_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,
entity_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')}")from casual_memory.extractors import LLMMemoryExtracter
from casual_llm import UserMessage, AssistantMessage
# Create extractors for user and assistant memories
user_extractor = LLMMemoryExtracter(model=model, source="user")
assistant_extractor = LLMMemoryExtracter(model=model, 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)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 TrueInput: 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
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)
- Architecture Guide - System design and concepts
- Examples - Working example code
# 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 -vClassification 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
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
Built with:
- casual-llm - LLM provider abstraction
- sentence-transformers - NLI models
- Inspired by research in semantic memory and conflict detection