Python SDK for the Human-Like Memory System — ACT-R based memory for AI agents
Project description
human-memory
Python SDK for the Human-Like Memory System — give your AI agents human-like memory based on the ACT-R cognitive architecture.
Unlike simple vector stores, this system mimics how the human brain actually works:
- Frequently used memories get stronger (activation from access history)
- Unused memories naturally fade (power-law decay)
- Emotional events persist longer (salience-modified decay)
- Context boosts related memories (spreading activation via knowledge graph)
- Repeated patterns become skills (consolidation extracts knowledge)
Installation
pip install human-memory
Requirements: Python 3.12+ | Only 2 dependencies: httpx, pydantic
Prerequisites
The SDK connects to a running human-memory server. Start it with:
git clone https://github.com/AbdulrahmanMasoud/human-memory.git
cd human-memory
docker compose up -d
The server runs at http://localhost:8000 by default.
Quick Start
from human_memory import Memory
# Connect to the memory server
memory = Memory("http://localhost:8000")
# Store a memory
result = memory.store("User prefers dark mode in all applications")
print(result.memory_id) # "a1b2c3d4-..."
print(result.activation) # 1.0 (initial activation)
# Search memories (ranked by ACT-R activation, not just similarity)
results = memory.search("what are the user's preferences?")
for r in results:
print(f" {r.content}")
print(f" activation: {r.activation:.2f} | similarity: {r.similarity:.2f}")
# Close when done
memory.close()
Core Concepts
How Activation Works
Every memory has an activation level that determines how easily it can be retrieved:
A = B + S + P + noise
- B (Base-level): Memories accessed recently and frequently have higher B. Follows a power-law decay — rapid drop at first, then slower.
- S (Spreading): If related concepts are active in the current context, connected memories get a boost.
- P (Partial matching): Similar but imperfect matches get adjusted.
- noise: Random variation, like real human recall.
The key insight: A memory accessed 10 times this week has much higher activation than one accessed once a year ago. This is computed mathematically, not heuristically.
Memory Lifecycle
Store --> Active (activation = 1.0)
|
Access --> Activation increases (access recorded)
|
Time passes --> Decay reduces activation (every 30 min)
|
Below threshold --> Decayed (no longer retrievable)
|
Consolidation --> Patterns extracted as semantic knowledge
API Reference
Initialize
from human_memory import Memory, AsyncMemory
# Sync client
memory = Memory(
base_url="http://localhost:8000", # Server URL
timeout=30.0, # Request timeout in seconds
)
# Async client (same API, all methods are async)
memory = AsyncMemory("http://localhost:8000")
Context Manager
# Sync -- auto-closes when done
with Memory("http://localhost:8000") as memory:
memory.store("auto-managed connection")
# Async
async with AsyncMemory("http://localhost:8000") as memory:
await memory.store("async auto-managed")
Storing Memories
result = memory.store(
content="User asked about Python performance optimization",
memory_type="episodic", # Optional: "episodic" (default), "semantic", etc.
context="support_chat", # Optional: context tag for the access record
)
Returns: MemoryCreated
| Field | Type | Description |
|---|---|---|
memory_id |
str |
UUID of the stored memory |
activation |
float |
Initial activation (1.0) |
created_at |
str |
ISO timestamp |
Searching Memories
results = memory.search(
query="Python performance", # Natural language query
top_k=5, # Max results (default: 7)
min_activation=-0.5, # Optional: override retrieval threshold
)
for r in results:
print(f"{r.content}")
print(f" activation: {r.activation:.3f}") # ACT-R activation score
print(f" similarity: {r.similarity:.3f}") # Cosine similarity to query
print(f" accesses: {r.access_count}")
Returns: list[SearchResult]
| Field | Type | Description |
|---|---|---|
memory_id |
str |
UUID |
content |
str |
Memory text |
activation |
float |
ACT-R activation (ranking key) |
similarity |
float |
Cosine similarity to query |
last_accessed |
str |
When last retrieved |
access_count |
int |
Total access count |
Important: Results are ranked by activation (recency + frequency + context), not just similarity. This is what makes the system cognitively plausible.
Recalling by ID
info = memory.recall("a1b2c3d4-...")
print(info.content) # "User prefers dark mode..."
print(info.access_count) # 6 (incremented by this recall)
Note: Every recall() records an access event, which strengthens the memory. This is the ACT-R reinforcement loop -- useful memories get stronger through use.
Returns: MemoryInfo
| Field | Type | Description |
|---|---|---|
memory_id |
str |
UUID |
content |
str |
Full text |
activation |
float |
Current activation |
memory_type |
str |
"episodic", "semantic", etc. |
created_at |
str |
Creation timestamp |
last_accessed |
str |
Last access timestamp |
access_count |
int |
Total accesses (including this one) |
Inspecting Full Metadata
detail = memory.inspect("a1b2c3d4-...")
print(f"Activation: {detail.activation:.4f}")
print(f"Salience: {detail.salience:.4f}")
print(f"Emotion: valence={detail.emotion_valence:.2f}, arousal={detail.emotion_arousal:.2f}")
print(f"Decay rate: {detail.decay_rate}")
print(f"Status: {detail.status}") # "active", "decayed", or "deleted"
print(f"Access history: {len(detail.access_history)} entries")
for access in detail.access_history[:5]:
print(f" {access.accessed_at} -- {access.context}")
Returns: MemoryDetail
| Field | Type | Description |
|---|---|---|
activation |
float |
Current ACT-R activation |
salience |
float |
Emotional salience (0-1) |
emotion_valence |
float |
Positive/negative (-1 to 1) |
emotion_arousal |
float |
Calm/excited (0 to 1) |
decay_rate |
float |
ACT-R d parameter (default 0.5) |
status |
str |
"active", "decayed", "deleted" |
access_history |
list[AccessRecord] |
Timestamped access log |
Forgetting
# Soft-delete a specific memory
deleted = memory.forget("a1b2c3d4-...") # Returns True
# The memory is now inaccessible
memory.recall("a1b2c3d4-...") # Raises MemoryNotFound
Listing All Memories
# Paginated list (includes active, decayed, and deleted)
items = memory.list(offset=0, limit=50)
for item in items:
print(f"[{item.status}] {item.content[:60]}... (activation: {item.activation:.2f})")
Returns: list[MemoryItem]
Knowledge Graph
The knowledge graph stores semantic memory -- general facts and relationships between concepts. It powers the spreading activation component of ACT-R.
Building the Graph
# Create concepts
memory.add_concept("Python", type="language", activation=0.9)
memory.add_concept("AI", type="field")
memory.add_concept("FastAPI", type="framework")
memory.add_concept("Web Development", type="field")
# Create typed relationships with weights
memory.add_relation("Python", "AI", "USED_FOR", weight=0.9)
memory.add_relation("FastAPI", "Python", "WORKS_WITH", weight=0.95)
memory.add_relation("FastAPI", "Web Development", "USED_FOR", weight=0.85)
Querying Concepts
concept = memory.get_concept("Python")
print(f"{concept.name} ({concept.type})")
print(f"Activation: {concept.activation}")
for rel in concept.relationships:
print(f" --{rel.relation_type}--> {rel.target} (weight: {rel.weight})")
Spreading Activation
Find related concepts by spreading activation through the graph:
# What's related to Python?
related = memory.spread(
concepts=["Python"], # Active concepts in current context
depth=2, # How many hops to traverse (1-4)
limit=10, # Max results
)
for r in related:
print(f" {r.name}: activation={r.activation:.2f}, spread={r.path_weight:.3f}")
# Output:
# AI: activation=0.80, spread=0.900
# FastAPI: activation=0.80, spread=0.950
# Web Development: activation=0.80, spread=0.808
How it works: When "Python" is the active context, activation spreads through the graph edges. AI gets a boost because Python->AI has weight 0.9. Web Development gets a smaller boost because it's 2 hops away (Python->FastAPI->Web Dev).
Returns: list[SpreadResult]
| Field | Type | Description |
|---|---|---|
name |
str |
Concept name |
activation |
float |
Concept's base activation |
path_weight |
float |
Accumulated spread weight |
Operations
Temporal Decay
Recalculates activation for all active memories using the ACT-R equation. Memories below the retrieval threshold are marked as decayed.
report = memory.decay()
print(f"Processed: {report.memories_processed}")
print(f"Decayed: {report.memories_decayed}")
Note: Runs automatically every 30 minutes. Use this for manual triggering.
Consolidation
Runs the 4-phase consolidation cycle (inspired by sleep):
- Replay -- boost high-salience recent memories
- Extract -- cluster similar episodes, LLM extracts semantic facts
- Prune -- global activation downscaling, archive weak memories
- Compile -- convert repeated patterns to procedural skills
report = memory.consolidate()
print(f"Replayed: {report.episodes_replayed}")
print(f"Facts extracted: {report.facts_extracted}")
print(f"Pruned: {report.memories_pruned}")
print(f"Downscaled: {report.memories_downscaled}")
Note: Runs automatically every 6 hours. Use this for manual triggering.
Strategic Forgetting
# Weaken memories not relevant to current goals
report = memory.forget_strategy(
"strategic_prune",
goals=["Python", "machine learning", "optimization"]
)
print(f"Memories affected: {report.memories_affected}")
# Archive weakest memories when store is full
report = memory.forget_strategy("capacity_overflow")
| Strategy | Description |
|---|---|
strategic_prune |
Weaken memories unrelated to specified goals |
capacity_overflow |
Archive weakest memories when capacity exceeded |
System Status
# Statistics
stats = memory.stats()
print(f"Total: {stats.total}")
print(f"Active: {stats.active}")
print(f"Decayed: {stats.decayed}")
print(f"Deleted: {stats.deleted}")
print(f"Avg activation: {stats.avg_activation:.3f}")
# Backend readiness
checks = memory.ready()
# {'postgres': 'ok', 'qdrant': 'ok', 'neo4j': 'ok'}
Async Client
AsyncMemory has the exact same API -- all methods are async:
import asyncio
from human_memory import AsyncMemory
async def main():
async with AsyncMemory("http://localhost:8000") as memory:
result = await memory.store("Async memory example")
results = await memory.search("async example")
await memory.add_concept("AsyncIO", type="concept")
stats = await memory.stats()
print(f"Total memories: {stats.total}")
asyncio.run(main())
Use with FastAPI
from fastapi import FastAPI
from human_memory import AsyncMemory
app = FastAPI()
memory = AsyncMemory("http://memory-server:8000")
@app.post("/chat")
async def chat(message: str):
# Retrieve relevant context
context = await memory.search(message, top_k=5)
# ... call LLM with context ...
# Store the interaction
await memory.store(f"User: {message}\nAssistant: {response}")
return {"response": response}
Error Handling
from human_memory import Memory
from human_memory.exceptions import (
MemoryNotFound,
ServiceUnavailable,
ValidationError,
HumanMemoryError,
)
memory = Memory("http://localhost:8000")
try:
memory.recall("nonexistent-id")
except MemoryNotFound:
print("Memory not found") # 404
try:
memory.store("") # Empty content
except ValidationError:
print("Invalid input") # 422
try:
memory.spread(["Python"]) # Neo4j down
except ServiceUnavailable:
print("Knowledge graph not available") # 503
Exception hierarchy:
HumanMemoryError # Base (any HTTP error)
MemoryNotFound # 404
ServiceUnavailable # 503
ValidationError # 422
Complete Example: AI Agent with Memory
from human_memory import Memory
def create_agent():
memory = Memory("http://localhost:8000")
# Build the agent's knowledge base
memory.add_concept("Python", type="language")
memory.add_concept("JavaScript", type="language")
memory.add_concept("Web", type="field")
memory.add_relation("JavaScript", "Web", "USED_FOR", weight=0.95)
memory.add_relation("Python", "Web", "USED_FOR", weight=0.6)
return memory
def agent_respond(memory: Memory, user_message: str) -> str:
# 1. Retrieve relevant memories
memories = memory.search(user_message, top_k=5)
context = "\n".join(
f"- {m.content} (relevance: {m.activation:.2f})"
for m in memories
)
# 2. Check knowledge graph for related concepts
try:
related = memory.spread(["Python"], limit=3)
for r in related:
context += f"\n- Related: {r.name} (weight: {r.path_weight:.2f})"
except Exception:
pass
# 3. Build prompt for LLM
prompt = f"""You are a helpful assistant with memory.
Relevant context from past interactions:
{context if context else "No relevant memories yet."}
User: {user_message}
Assistant:"""
# 4. Call your LLM here
response = call_your_llm(prompt)
# 5. Store this interaction
memory.store(f"User asked: {user_message}")
return response
# Usage
memory = create_agent()
print(agent_respond(memory, "How do I optimize Python code?"))
print(agent_respond(memory, "What about JavaScript for web apps?"))
# Maintenance (also runs automatically in background)
memory.decay()
memory.consolidate()
stats = memory.stats()
print(f"Agent has {stats.active} active memories")
memory.close()
All Models
| Model | Returned by | Key fields |
|---|---|---|
MemoryCreated |
store() |
memory_id, activation, created_at |
SearchResult |
search() |
memory_id, content, activation, similarity, access_count |
MemoryInfo |
recall() |
memory_id, content, activation, memory_type, access_count |
MemoryDetail |
inspect() |
All of MemoryInfo + salience, emotion_valence, emotion_arousal, decay_rate, status, access_history |
MemoryItem |
list() |
memory_id, content, activation, salience, status, access_count |
Stats |
stats() |
total, active, decayed, deleted, avg_activation |
Concept |
add_concept(), get_concept() |
name, type, activation, relationships |
Relation |
add_relation() |
source, target, relation_type, weight |
SpreadResult |
spread() |
name, activation, path_weight |
DecayReport |
decay() |
memories_processed, memories_decayed |
ConsolidationReport |
consolidate() |
episodes_replayed, facts_extracted, memories_pruned, memories_downscaled |
ForgetReport |
forget_strategy() |
strategy, memories_affected |
AccessRecord |
Inside MemoryDetail |
accessed_at, context |
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 human_memory_sdk-0.1.1.tar.gz.
File metadata
- Download URL: human_memory_sdk-0.1.1.tar.gz
- Upload date:
- Size: 25.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
023026d341bc1cd8463ad9bcf59989444beffb6e22cb46dd9131c52d80e74dd4
|
|
| MD5 |
85670c89d8f682b1e2fa978cf3b81e4a
|
|
| BLAKE2b-256 |
5b6293deacfa8b31b300d3be4dee72494bd9a4e284d9af87b1a298691751e1f7
|
File details
Details for the file human_memory_sdk-0.1.1-py3-none-any.whl.
File metadata
- Download URL: human_memory_sdk-0.1.1-py3-none-any.whl
- Upload date:
- Size: 12.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36e76be4ebebe377653e53c8562be06fb927d106b8e26f9c39a5ea7c911bd958
|
|
| MD5 |
77c92088755bfc7ad49afab43939f943
|
|
| BLAKE2b-256 |
72f38c80454c5c35bc32588d1396bcb4620a77f5a238b6201a2588f84599ba4e
|