Your agent’s memory, written down.
Two-tier memory for AI agents. Every turn is stored, every durable fact is extracted and classified, and recall is fused from three signals then trimmed to the token budget you set. You can read all of it. No API key and no database to start.
npm install actrone-memory · pip install actrone-memory
| memory | tier | sensitivity | topic |
|---|---|---|---|
| Name is Alex | L2 | pii | identity |
| Works as a Python developer | L2 | none | role |
| Prefers concise answers over long explanations | L2 | low | preference |
| alex@acme.com | L2 | pii | contact |
| On-call rotation happens every week | L2 | none | operations |
| Feeling anxious about the product launch | L2 | sensitive | wellbeing |
retrieveContext("assistant", "s1", "when does the on-call rotation happen", 4096)
→ top result: On-call rotation happens every week
→ after a session reset: 0 turns, 6 facts still there
- licence
- MIT, free forever
- to start
- no services, no API key, nothing leaves the machine
- tiers
- L1 session turns · L2 semantic
- recall
- dense and lexical, RRF fused, recency weighted
- classification
- none · low · pii · sensitive
- stores
- in-memory default; Redis, Postgres, Qdrant, pgvector adapters; or your own
- quality eval
- ships one. recall@5 0.929, offline, CI-gated
- integrations
- 16 frameworks in Python, 11 in TypeScript
Store a turn. That is the whole setup.
MemoryManager.create() with no arguments gives you an in-process store and a dependency-free embedder. No Redis, no Qdrant, no OpenAI key. Swap any of the three later by passing an adapter, one of the built-in ones for Redis, Postgres, Qdrant or pgvector, or a store you wrote yourself against the same interface. The manager and your calling code do not change.import { MemoryManager } from "actrone-memory";
async function main() {
// In-memory store plus a local embedder. Nothing to run, nothing to configure.
const mm = await MemoryManager.create();
await mm.storeTurn(
"assistant",
"s1",
"My name is Alex and I work as a Python developer.",
"Good to meet you, Alex.",
);
}
main();import asyncio
from actrone_memory import create_memory_manager
async def main():
# In-memory store plus a local embedder. Nothing to run, nothing to configure.
async with create_memory_manager() as memory:
await memory.store_turn(
agent_id="assistant",
session_id="s1",
user_message="My name is Alex and I work as a Python developer.",
assistant_message="Good to meet you, Alex.",
)
asyncio.run(main())Every fact carries its sensitivity
- none2 in the demo
Ordinary project facts and preferences. Stored plainly, always recallable.
- low1 in the demo
Identifying but not private. Role, employer, stated preference.
- pii2 in the demo
Personal data. Name, email, phone. Masked in place when redaction is on.
- sensitive1 in the demo
Health, finance, credentials, wellbeing. Masked with pii, and worth a policy.
Recall is fused, not guessed
dense
embedding similarity
lexical
bm25 term overlap
recency
decay over turns
fused · rrf
then pruned to budget
Context that fits, every time
- system prompt 320
- recent turns (L1) 1 480
- memories (L2) 812
- unused 1 484
The same call, in both languages
const ctx = await mm.retrieveContext(
"assistant",
"s1",
"when does the on-call rotation happen",
4096,
);
ctx.recentTurns;
ctx.episodicMemories;ctx = await memory.retrieve_context(
agent_id="assistant",
session_id="s1",
query="when does the on-call rotation happen",
token_budget=4096,
)
ctx.recent_turns
ctx.episodic_memoriesFull transcript
A fact lands. It is classified as it arrives. Teach an agent who you are today. By tomorrow's session, it has forgotten. Actrone Memory. Open source, local first. Teach it once. It decides what is worth keeping, and classifies how sensitive each fact is, the moment it lands. Names and emails, PII. Health and mood, sensitive. Governance aware, in the free library. New session. It forgets the conversation, not you. Hybrid recall brings the right fact back: dense, keyword and recency, fused, inside your token budget. One toggle, and the sensitive memories mask themselves. TypeScript or Python. The same behaviour, verified identical. And it ships its own quality eval, so that number cannot quietly rot. Free and open, forever. Install it, star it, and bring it into production when you are ready.
What this does not do
PII tagging is not PII protection on a cloud model
The
piiandsensitivetags are produced by the extraction step, and that step sees the raw text. With the defaults, extraction and embedding run in-process, so nothing leaves your machine and the protection is real. Point either at a cloud provider and the raw text, including everything taggedpii, is sent to that provider. This library does not tokenise before inference. The hosted platform does.It stores and recalls. It does not build a knowledge graph
Turns and extracted facts, retrieved well. It does not do the entity consolidation or temporal graph reasoning some alternatives do. If your problem is "my agent forgets", this fits. If your problem is "resolve conflicting facts about an entity over time", it does not, yet.
We publish our own eval, not a head-to-head
The library ships a quality eval that runs offline in one command and is gated in CI at
recall@5 >= 0.85. It currently scores 0.929. Two things that number is not. It is not a public-dataset result: the bundled set is LongMemEval-style, not LongMemEval itself. And it is not a comparison: we have never run Mem0 or Zep through the harness, so there are no head-to-head figures here, and there will not be until we have run them. The zero-dependency default embedder is keyword overlap, so paraphrases score low.The two languages are at parity in the core, not in the adapters
MemoryManagerbehaves identically in both. The framework integrations do not: Python ships deep adapters as optional extras, and TypeScript ships structural helpers. If you need a specific framework adapter, check the integrations page before you commit.