Skip to content

Agentic Memory

agentic_memory is a Python library that gives an agent six kinds of memory behind one façade, AgentMemory:

Store Model Backends
mem.episodic EpisodicMemory PostgreSQL + Weaviate + ArangoDB
mem.semantic SemanticMemory PostgreSQL + Weaviate + ArangoDB (knowledge graph)
mem.procedural ProceduralMemory PostgreSQL + Weaviate + ArangoDB
mem.reflective ReflectiveMemory PostgreSQL + Weaviate + ArangoDB
mem.reward RewardMemory PostgreSQL + Weaviate + ArangoDB
mem.context_kv plain dicts Redis

How Storage Works

Every write to one of the five long-term types goes to three backends at once:

store(memory)
     │
     ├──► PostgreSQL   structured fields, exact lookups, filters, aggregates
     ├──► Weaviate     embedding vector (cosine similarity search)
     └──► ArangoDB     graph node (+ edges for relationships)

Reads use the most suitable backend:

Operation Backend
retrieve(id), filters, aggregates PostgreSQL
search(query) Weaviate finds the nearest IDs, then each record is loaded from PostgreSQL. score = 1 − cosine distance
Relationships (links, paths, neighbours, provenance) ArangoDB traversal

Context KV is different: it lives only in Redis (one hash per agent session).

Writes are not transactional across backends. If one backend fails mid-write, the others may already hold the record.


Installation

Requires Python ≥ 3.8 and the four databases (see Deployment).

cd agent-memories/agentic-memory
pip install -e ".[dev]"

Dependencies: weaviate-client>=4.0, python-arango>=8.0, psycopg2>=2.9, openai>=1.0, redis>=5.0.


Configuration

from agentic_memory import (
    AgentMemory, MemoryConfig, PostgresConfig, WeaviateConfig, ArangoConfig, RedisConfig,
)

config = MemoryConfig(
    postgres=PostgresConfig(host="db.example.org", port=30432, database="agent_memory",
                            username="postgres", password="..."),
    weaviate=WeaviateConfig(host="db.example.org", port=30880, grpc_port=30851,
                            collection_prefix="AgentMemory", embedding_dim=1536),
    arango=ArangoConfig(url="http://db.example.org:30529", username="root",
                        password="...", database="agent_memory"),
    redis=RedisConfig(host="db.example.org", port=30637, password="...", db=0),
    embedding_model="text-embedding-3-small",
    top_k=5,
)
mem = AgentMemory(config)
Config Field Default
PostgresConfig host, port, database, username, password localhost, 5432, agent_memory, postgres, ""
WeaviateConfig host, port, grpc_port localhost, 8080, 50051
collection_prefix AgentMemory (collections AgentMemoryEpisodic, AgentMemorySemantic, …)
embedding_dim 1536 (passed to the embedding provider)
ArangoConfig url, username, password, database http://localhost:8529, root, "", agent_memory
RedisConfig host, port, password, db localhost, 6379, "", 0
MemoryConfig embedding_model text-embedding-3-small
top_k 5 (stored on the config; search methods take their own top_k)

AgentMemory() with no arguments connects to all four databases on localhost with default ports.

On start-up the library creates what it needs:

  • PostgreSQL tables and indexes (CREATE TABLE IF NOT EXISTS).
  • One Weaviate collection per memory type (HNSW index, cosine distance, no vectorizer).
  • The ArangoDB database plus its node and edge collections.

Embeddings

Vectors come from an EmbeddingProvider. By default it calls the OpenAI embeddings API (text-embedding-3-small) and reads OPENAI_API_KEY from the environment.

from agentic_memory.embeddings import EmbeddingProvider

# Default: OpenAI
mem = AgentMemory(config, openai_api_key="sk-...")       # or set OPENAI_API_KEY

# Explicit provider
embedder = EmbeddingProvider(model="text-embedding-3-large", dim=3072, api_key="sk-...")
mem = AgentMemory(config, embedder=embedder)

# Any embedding function
def my_embed(text: str) -> list[float]:
    ...
mem = AgentMemory(config, embedder=EmbeddingProvider(embed_fn=my_embed, dim=768))

EmbeddingProvider provides embed(text) and embed_batch(texts). Use the same model (and dimension) for the lifetime of a Weaviate collection, because vectors from different models are not comparable.

What gets embedded for each type:

Type Embedded text
Episodic content
Semantic "{subject} {predicate} {object} {content}"
Procedural "{name} {description} {step.action step.expected_outcome …}"
Reflective "{content} {lesson} {improvement_suggestion}"
Reward "{state_description} {action} {outcome}"

The AgentMemory Façade

Write helpers

from agentic_memory.models import Outcome

ep = mem.remember_episode(
    "User asked about the weather in Paris",
    session_id="sess-001", agent_id="weather-agent",
    context={"user": "alice", "channel": "chat"},
    outcome=Outcome.SUCCESS, importance=0.8, metadata={"lang": "en"},
)

fact = mem.know_fact("Paris", "is_capital_of", "France", confidence=1.0, source="wikipedia")

proc = mem.learn_procedure(
    "send_email",
    steps=[
        {"action": "compose", "parameters": {"to": "..."}, "expected_outcome": "draft ready"},
        {"action": "send", "parameters": {}, "expected_outcome": "email sent"},
    ],
    description="Send an email to a contact",
    trigger_conditions=["user wants to send an email"],
)

ref = mem.reflect(
    "I should confirm location before giving weather",
    lesson="Clarify ambiguous location references",
    improvement_suggestion="Ask 'Which Paris?' when unclear",
    source_episode_id=ep.id, confidence=0.9,
)

rw = mem.record_reward("low battery warning shown", "recharge_now", reward=1.0,
                       outcome="battery restored", policy="energy")
Method Returns Notes
remember_episode(content, *, session_id, agent_id, context, outcome, importance, metadata) EpisodicMemory outcome defaults to Outcome.UNKNOWN, importance to 0.5
know_fact(subject, predicate, object_, *, content, confidence, source, metadata) SemanticMemory content defaults to "subject predicate object"
learn_procedure(name, steps, *, description, trigger_conditions, metadata) ProceduralMemory steps are dicts with action, parameters, expected_outcome; step_order follows list order
reflect(content, lesson, *, improvement_suggestion, source_episode_id, confidence, metadata) ReflectiveMemory Linking to an episode creates a provenance edge
record_reward(state_description, action, reward, *, outcome, policy, context, metadata) RewardMemory policy defaults to "default"

Each helper builds the model and calls the store's store(). You can also build models yourself and call mem.<type>.store(memory).

Searching everything: recall

results = mem.recall("weather questions", top_k=5)
# {"episodic": [...], "semantic": [...], "procedural": [...], "reflective": [...], "reward": [...]}

results = mem.recall("step", top_k=5, agent_id="planner-agent", session_id="session-99")
# ... plus "context": [{"key": "...", "value": {...}}, ...]

recall runs a vector search on each of the five long-term types. When agent_id or session_id is given, it also adds a "context" list: the session's context entries whose key or value contains the query string (case-insensitive substring match, up to top_k).

Context helpers

mem.set_context("status", {"ready": True}, agent_id="planner", session_id="s1", ttl_seconds=600)
mem.get_context("status", agent_id="planner", session_id="s1")   # {"ready": True}

These are shorthands for mem.context_kv.set(...) / .get(...). See Context KV Memory.

Lifecycle

with AgentMemory(config) as mem:   # closes PostgreSQL, Weaviate and Redis on exit
    ...

mem = AgentMemory(config)
mem.close()

Common Store Interface

Every long-term store (episodic, semantic, procedural, reflective, reward) implements:

Method Description
store(memory) -> str Write to PostgreSQL, Weaviate and ArangoDB; returns the memory ID
retrieve(memory_id) Load by ID from PostgreSQL (None if missing)
search(query, top_k=5) Vector search; results carry .score (higher is more similar)
update(memory) Re-embed and overwrite in all three backends
delete(memory_id) Remove from all three backends (graph edges are not removed)

All models share the BaseMemory fields:

Field Type Default
id str random UUID
content str ""
metadata dict {}
embedding list[float] \| None set on store/update
created_at datetime datetime.utcnow()
score float \| None set by search

Enums: MemoryType (episodic, semantic, procedural, reflective, reward) and Outcome (success, failure, neutral, unknown).


Storage Layout

PostgreSQL tables

Table Holds Indexed columns
episodic_memories episodes session_id, agent_id, outcome
semantic_memories triples subject, predicate
procedures procedures name
procedure_steps steps (FK → procedures, cascade delete) procedure_id
reflective_memories reflections source_episode_id
reward_memories reward tuples action, policy

context, metadata, trigger_conditions and parameters are stored as JSONB.

Weaviate collections

<prefix>Episodic, <prefix>Semantic, <prefix>Procedural, <prefix>Reflective, <prefix>Reward. Each object has the memory's ID as its UUID, a memory_id property and the embedding vector.

ArangoDB collections

Collection Kind Contents
episodic_nodes, semantic_nodes, procedural_nodes, reflective_nodes, reward_nodes vertex One node per memory (_key = memory ID) with its main fields
entity_nodes vertex Knowledge-graph entities (one per distinct subject/object)
memory_relations edge Episode ↔ episode links, reward state transitions
entity_relations edge Subject → object edges of semantic facts (predicate, memory_id, confidence)
episode_reflection edge Episode → reflection provenance

Redis

One hash per session: key <agent_id>__<session_id>, one field per context key, values stored as JSON.


Testing

cd agent-memories/agentic-memory
pytest

The tests mock all four backends and do not need running databases. Runnable examples against real databases are in agent-memories/examples/ (example_episodic.py, example_semantic.py, example_procedural.py, example_reflective.py, example_reward.py, example_context_kv.py, example_openai_embeddings.py). Connection settings come from environment variables read by examples/db_config.py (POSTGRES_HOST, WEAVIATE_HOST, ARANGO_URL, REDIS_HOST, …).