Loading docs…
We're open source. If actrone-memory has been useful to you, a star on GitHub means a lot to us.
Star the projectLoading docs…
Actrone uses a two-tier memory system: a hot L1 tier for fast recent-turn access, and a semantic L2 tier for long-term recall. Both are fetched in parallel on every turn. In-memory by default, Redis (L1) and Qdrant (L2) are an opt-in production backend, not a requirement to start.
Tip
actrone-memory reference and the TypeScript actrone-memory reference.The open-source memory library ships for both ecosystems and they are kept in lock-step: actrone-memory, published under that one name on PyPI for Python and on npm for TypeScript/JavaScript. Same two-tier architecture, same 4-phase retrieval, same result shapes. Only the surface idioms differ.
Note
snake_case in Python (max_session_turns) and camelCase in TypeScript (maxSessionTurns). Result fields follow the same convention (recent_turns / recentTurns). Both libraries run with zero services by default (an in-memory store and a local embedder), so you can start without Redis, Qdrant, or an API key in either language.Flat vector search over all memories is slow and expensive. Flat recent-turn storage has no semantic recall. Two tiers give you <1ms recent access and ~10ms semantic search, combined in a single parallel fetch with budget-aware pruning.
Both libraries ship an InMemoryStore that backs L1 and L2 with zero external services. This is what MemoryManager.create() uses with no arguments, in Python and TypeScript alike. It's process-local and doesn't survive a restart, which is fine for local development or a single-process app but not for production.
For production, pick the stores you already run. Redis (or any Redis-protocol server such as Valkey) and Postgres back the hot tier; Qdrant and Postgres + pgvector back the semantic tier. In Python, the Redis + Qdrant pair has an env shortcut: install the redis/qdrant/production extras and set ACTRONE_BACKEND=redis_qdrant. Otherwise pass the stores to MemoryManager.create() in either language. Every method call is identical whichever you choose: swapping backends never touches your application code.
Nothing about those adapters is privileged. L1Store and L2Store are small interfaces (a typing.Protocol in Python), so a store you write yourself plugs into the same seam, and the library ships a conformance suite so you can prove it behaves like the built-in ones. See Configuration.
Recent turns are capped at max_session_turns (default 50). On the Redis backend they're stored as a Redis List with TTL session_ttl_hours (default 24h, Python only, the TypeScript core has no built-in session TTL); on the in-memory backend they live in a process-local map. Reading the recent turns from Redis is a single range read, typically well under a millisecond.
Note
L2 holds long-term memories: facts you inject with inject_memory / injectMemory, session summaries (Python), and facts the opt-in extractor distils. Recent turns stay in L1. Each memory is embedded, by default with a local embedder (a local ONNX model when the [onnx] extra or the fastembed package is installed, otherwise a dependency-free hashing embedder that matches shared keywords, see Configuration), or OpenAI's text-embedding-3-small if you opt in, and indexed for semantic search. By default, results are ranked by Reciprocal Rank Fusion across dense (vector) and lexical (BM25) search plus recency (see Retrieval pipeline for the exact formula and the classic weighted-blend fallback). Only entries above the relevance threshold are returned; each embedder carries the threshold it was calibrated for (0.63 for bge-small, 0.3 for hashing).
The 4-phase retrieval pipeline splits the token budget you pass in by fixed fractions:
Each share is filled in priority order and pruned to fit; an unused share is not handed to the other. Retrieval never touches the part reserved for your system prompt.
In Python, after summarise_after_turns turns (default 20), a background task compresses the session and writes the summary back into L2 (Qdrant in production, in-memory by default). The summary is written by gpt-4o-mini when the OpenAI provider is configured, and is otherwise an extractive summary taken from the conversation itself, so no data leaves your machine by default. The raw turns are retained. A per-session cooldown (default 300s) prevents concurrent re-summarisation. The TypeScript library does not summarise automatically.
Beyond raw turns and summaries, extract_memories / extractMemories mines a session's recent turns for durable, atomic facts and stores each as its own L2 entry (content_type="fact" / contentType: "fact", source="extracted"). This is LLM-gated: it requires extract_facts=True (Python) with the OpenAI embedding provider, or a configured FactExtractor passed to MemoryManager.create() (TypeScript). It's off by default in both languages because it costs an extra LLM call per extraction.
# Python: requires extract_facts=True with embedding_provider="openai"
fact_ids = await memory.extract_memories(agent_id="support-bot", session_id="s1")Every stored memory carries two governance fields alongside its content, a source recording where it came from (TypeScript types this as a canonical set: "user", "assistant", "tool", "summary", "injected", "extracted", "reflection", "imported", "unknown", or a namespaced string like "tool:web_search"; Python leaves it a free-form string, defaulting to "unknown") and a sensitivity classification ("none" → "low" → "pii" → "sensitive") for filtering and retention policy. Both default to the least-sensitive value for memories written before this was tracked, and are set automatically by extract_memories' LLM classifier, or explicitly when you call inject_memory / injectMemory directly.
This is also what backs local right-to-erasure: erase_agent_memories / eraseAgentMemories irreversibly deletes an agent's L2 store (and, if you pass a session_id, that session's L1 turns too). It's a hard local delete in the OSS library, the seed the hosted platform graduates into cryptographically provable erasure.
# Python
await memory.erase_agent_memories(agent_id="support-bot", session_id="s1")// TypeScript
await memory.eraseAgentMemories("support-bot", "s1")Warning
store_turn / storeTurn is written to L1/L2 exactly as given, in plain text (or as a plain vector embedding), read that carefully if you plan to store real user conversations.Concretely:
sensitivity="none", whether or not they actually contain personal data, the field only reflects reality when something else sets it.extract_memories / extractMemories fact extractor (off by default, LLM-gated): its extracted facts get a real sensitivity value from the classifier prompt.sensitivity is only ever accurate if you set it explicitly on inject_memory / injectMemory.If you're storing real conversations, the practical pattern is: redact or scrub sensitive fields before calling store_turn, turn on fact extraction if you want automatic sensitivity tagging on distilled facts, and use erase_agent_memories (above) to fulfil a right-to-erasure request. Automatic PII scanning, redaction, and policy-driven retention are part of the governed hosted platform, not this open-source library.