memory-core
English | 中文
A local-first, framework-agnostic long-term memory core for AI companions.
Status: pre-alpha (M2). Storage, hybrid recall, the bundle format, LLM consolidation, an MCP server, an AstrBot adapter, the daily diary and self-image, memory metabolism (fading, merging, hard forgetting), seeing pictures, a sense of time and inner state, what the being can do, and a read-only dashboard work and are tested. APIs may change.
Why
An AI companion should still be itself after you switch chat framework, LLM, or embedding model. Most memory plugins keep their data in a plugin-specific format, and many treat vectors as the memory itself — but vectors belong to one embedding model and become useless when the model changes.
memory-core is built on a few rules:
- Text is the memory; vectors are only an index. Raw experiences and readable memories are kept
forever; vectors can be recomputed with any model at any time (
memory-core reindex). - Raw experiences are append-only. Everything else (memories, diaries, profiles, indexes) is derived from them and records its sources, so it can be rebuilt, audited, or deleted along the provenance chain.
- Framework-agnostic. A Python library (and soon a local MCP server); chat frameworks connect through thin adapters.
- Model-agnostic. Any OpenAI-compatible endpoint for embeddings (Ollama, vLLM, LM Studio, OpenAI...).
- Local-first. One SQLite file plus a folder. No telemetry, no network calls except to the model endpoints you configure.
- Portable. A versioned, plain-text, git-friendly export format — the soul bundle (SPEC.md).
- Privacy by scope. Every memory has a visibility scope, enforced in code: what happened in one group chat is not brought up in another.
Concepts
| What | Rebuildable? | |
|---|---|---|
| Episode | A raw experience: a message received or sent, something observed (a web page, a video), an action | No — the source of truth |
| Memory | A derived, one-sentence memory: fact, event, relation, reflection, diary, preference, lexicon (slang), style | Yes, from episodes |
| Entity | A person, group, place, work... with aliases (account IDs, nicknames) | — |
| Identity | Markdown "core memory" documents: self.md, owner.md, now.md |
Hand-edited |
| Index | Full-text (SQLite FTS5) and vectors, keyed by model | Yes, always |
Scopes: public (anywhere), self (its own feelings), owner (only in private with the owner),
space:<space> (only in that place).
Quick start
pip install "memory-core-lib[zh]" # zh = jieba for better Chinese full-text search (optional); import memory_core
memory-core init ./soul
memory-core record ./soul --space web --source browser --kind observation --content "Watched a video about Genshin"
memory-core remember ./soul --scope public --kind fact --text "Genshin Impact is an open-world game"
memory-core recall ./soul "Genshin" --space web
memory-core export ./soul ./soul-bundle # portable plain-text backup
Semantic recall: run an embedding model locally, e.g. ollama pull bge-m3, then in soul/config.toml:
[embedding]
provider = "openai-compatible"
base_url = "http://127.0.0.1:11434/v1"
model = "bge-m3"
and memory-core reindex ./soul.
Python
from memory_core import MemoryCore, Episode, Memory, Audience
with MemoryCore.open("./soul") as core:
ep = core.record(Episode(kind="message_in", content="I don't eat coriander", space="chat:group:1",
source="qq", actor="qq:1001"))
core.add_memory(Memory(kind="preference", text="Ming doesn't eat coriander",
scope="space:chat:group:1", subjects=["qq:1001"], sources=[ep.id]))
for hit in core.recall("coriander", Audience(space="chat:group:1")):
print(hit.score, hit.memory.text)
Consolidation
Episodes become memories through a chat model (any OpenAI-compatible endpoint, or your framework's own model):
# soul/config.toml
[llm]
base_url = "https://api.deepseek.com/v1"
model = "deepseek-flash"
api_key_env = "DEEPSEEK_API_KEY" # the key stays in an environment variable, not in the file
[owner]
spaces = ["qq:private:<owner id>"] # private chats with the owner: their memories get the "owner" scope
memory-core consolidate ./soul
For each place with new episodes, the model sees the new messages and the related memories it already has, and
proposes to add a memory, update one (the old one is kept, marked superseded), or nothing. The model
proposes, code decides: it can never widen visibility — memories about people stay in the place they were learned
(or owner), only memories about no person can become public. In a public space ([spaces] public, default
["web"]: what the being read online on its own, e.g. with
surfing-the-web) what it saw becomes public, while memories
drawn from its own thoughts there stay with the owner. A place's cursor only advances after its batch
was applied, so a failed call loses nothing.
Diary and self-image
A being grows a sense of itself from what it lived (memory_core.reflect):
from memory_core.reflect import day_window, update_self_image, write_diary
start, end = day_window(day, boundary_hour=4) # late nights belong to the day before
write_diary(core, llm, day, start, end, persona=identity_text, owner_spaces=[...], owner_title="Dad")
update_self_image(core, llm, day, persona=identity_text)
- Diary: one first-person entry per day from that day's episodes (a
diarymemory with theownerscope, also written todiary/<date>.mdfor reading). Idempotent;force=Truerewrites it (the old one is superseded). - Self-image:
identity/self-image.md, a short "how I see myself" rewritten from the recent diaries. It is the being's own layer: it must not contradict the core identity (identity/self.md, which only the owner edits) and carries no private details of anyone. Previous versions are kept inidentity/history/.
The prompts are Chinese for now; the model answers in the language of its persona and experiences.
Memory metabolism
A memory that only grows gets worse. memory_core.metabolism and MemoryCore keep it healthy:
- Fading: a memory's strength halves every 30 days it is not recalled (recalling it strengthens it again).
fade(core)archives weak memories; important ones (importance ≥ 0.8) and diaries are never archived. Archived memories no longer come up by themselves, but a deliberate search (recall(..., include_archived=True)) finds them, and that makes them active again. - Merging:
merge_duplicates(core, llm)finds similar memories in the same scope by vector similarity and lets the model decide whether they record the same thing; if so they become one memory keeping every detail (MemoryCore.merge; the originals stay, marked superseded). Groups kept apart are not asked about again. - Hard forgetting:
core.forget(ids, with_sources=True)erases every version of a memory, the episodes it came from, and every other memory derived from those episodes (e.g. that day's diary, which can then be written again from what is left). The only exception to append-only episodes. - Write gate for the core identity: the being may only
propose(...)a change toidentity/self.md;resolve(..., approve=True)by the owner appends it under a dated section and keeps the previous version (memory_core.proposals).
MCP server
Any MCP client (Claude Code, DeepSeek Harness, Codex CLI, ...) can share the same memory:
memory-core mcp ./soul # stdio; add --read-only for clients that should only read
{"mcpServers": {"memory": {"command": "memory-core", "args": ["mcp", "/path/to/soul"]}}}
Tools: recall, recent, profile, stats, record, remember, consolidate. Identity documents are exposed
as resources (identity://self, ...). Every tool that returns memories takes the current space and only returns
what is visible there.
Adapters
- AstrBot — astrbot_plugin_memory_core (its own repository since 0.6.0, in AstrBot's plugin market as 记忆核心): records every visible message and the bot's own
answers, uses the identity documents as the persona, injects recalled memories before each reply,
consolidates periodically with AstrBot's own chat model, writes the daily diary and self-image, runs the daily
memory metabolism, gives the model
memory_recall/memory_remember/identity_proposetools, and the owner commands to forget and to approve proposals; a read-only dashboard (overview, memories with their sources and versions, recall explained, a graph of who and what the memories are about).
Roadmap
| M0 ✅ | Storage, append-only episodes, hybrid recall (full-text + vectors + entities), scopes, soul bundle, tests |
| M1 ✅ | Consolidation (add / update / supersede, entity aliases), MCP server, AstrBot adapter |
| M2 ✅ | Diary and self-image (0.2.0); fading and archiving (0.3.0), merging duplicates, hard forget along provenance, identity proposals |
| M3 | Public benchmarks (LoCoMo, LongMemEval), docs |
| M4 🔄 | Experience collectors: public spaces (0.4.0) and surfing-the-web (reading public sites on its own); next: a real browser, video sites |
| M5 | Lexicon (slang/meme) learning, style learning, identity proposals with rollback, inner monologue |
Acknowledgements
Design ideas from Generative Agents (memory stream, retrieval scoring, reflection), mem0 (add / update / delete consolidation), Graphiti (bi-temporal facts), Letta (core vs archival memory), dsh-memory (raw text as source of truth, layered write gates), and the AstrBot plugins LivingMemory and self_learning (ideas only; no code copied).
License
Metadata
Release files for memory-core-lib 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| memory_core_lib-0.6.0.tar.gz | 88.4 kB | With provenance | Details for memory_core_lib-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| memory_core_lib-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for memory_core_lib-0.6.0-py3-none-any.whl |
Total release size: 162.1 kB
Release files / memory_core_lib-0.6.0.tar.gz
| Download URL | memory_core_lib-0.6.0.tar.gz |
|---|---|
| Size | 88.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99000bef7c51e9bb13b2600b2711ef434cb463ceba038a66a5857977f3570843
|
|
BLAKE2b-256 checksum How to use checksums |
42fa88ef988a70a206dcc83351392cd62d2033d891c38dc7f0160931a015baa5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / memory_core_lib-0.6.0-py3-none-any.whl
| Download URL | memory_core_lib-0.6.0-py3-none-any.whl |
|---|---|
| Size | 73.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a0dc0ddbfb568635a4c4c1dfdc4d6a02204dada13242952599a1122c6003d712
|
|
BLAKE2b-256 checksum How to use checksums |
e74ea1d711a6f9e1f73cca33d75a8ff7ac5f61c9e788661ff6934ab519ac8f0a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for memory-core-lib 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for memory_core_lib-0.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.