langchain-membase
Membase long-term memory for LangChain and LangGraph (Python).
Membase keeps a person's memory in their own account: named Memories (containers) that learn from their notes, files and conversations, plus a profile of standing facts. This package gives a LangChain app three ways to use it:
| Piece | LangChain type | What it does |
|---|---|---|
MembaseRetriever |
BaseRetriever |
a Membase search as Documents, each citing its Memory in metadata["container_name"] |
MembaseToolkit (membase_tools()): MembaseSearchTool, MembaseSaveTool, MembaseProfileTool |
BaseToolkit / BaseTool |
agent tools: search, save (only when the user asks), read the profile |
MembaseRecallMiddleware |
LangChain v1 AgentMiddleware |
time-boxed recall added to the system message before the model call of a create_agent agent |
There is deliberately no delete or forget tool, and the package never confirms a
destructive action. It also does not implement LangGraph's BaseStore: Membase is a memory
that learns, not a key-value store (there is no get-by-key), so it is offered as retrieval and
tools instead.
Install
pip install langchain-membase # retriever + tools (needs langchain-core)
pip install 'langchain-membase[agents]' # + the create_agent middleware (needs langchain>=1.1)
Python 3.10+. Get a key in the Membase app under Connect › Developer keys and set it:
export MEMBASE_API_KEY=mbk_...
A Read key can search and read the profile (if its owner ticked it); saving needs a Read & write key. One key is one person's account.
Retriever
from langchain_membase import MembaseRetriever
retriever = MembaseRetriever(k=5) # MEMBASE_API_KEY from the environment
docs = retriever.invoke("what did we decide about the ledger")
for d in docs:
print(d.metadata["container_name"], "·", d.page_content)
k(1–50) is the number of passages in all; unset lets Membase cover every Memory in reach.container="mv-…"limits the search to one Memory (its id fromclient.containers.list()). Both can also be passed per call:retriever.invoke(q, k=3, container="mv-…").- If no passage comes back and some Memory could not answer (no model, dormant), the
retriever raises
MembaseUnavailableErrorinstead of returning[]: that is not evidence of "no memory". When some Memories answered, it returns their passages and logs the rest. - A
403(membase.PermissionDeniedError) means the key's owner narrowed or revoked it; do not retry with that key.
It works anywhere a retriever does, e.g. in an LCEL chain:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
def cite(docs):
return "\n".join(f"[{d.metadata['container_name']}] {d.page_content}" for d in docs)
prompt = ChatPromptTemplate.from_template("Memory:\n{memory}\n\nQuestion: {question}")
chain = {"memory": retriever | cite, "question": RunnablePassthrough()} | prompt | llm
Agent tools
from langchain.agents import create_agent
from langchain_membase import MembaseToolkit
agent = create_agent("openai:gpt-5", tools=MembaseToolkit().get_tools()) # or membase_tools()
agent.invoke({"messages": [{"role": "user", "content": "What did we pick for the ledger?"}]})
| Tool | Arguments | Notes |
|---|---|---|
membase_search |
query, optional container (a Memory's name) |
passages as - [Memory name] text; the raw response is the ToolMessage artifact; Memories that could not answer are reported |
membase_save |
content (the user's words), static, optional container |
the description tells the model to save only when the user asks; static=true records a standing fact in the profile; with several Memories in reach it needs a container (or a default container= id on the tool) and says which names exist |
membase_profile |
optional topic |
standing and recently changed facts; only for a key whose owner ticked the profile |
MembaseToolkit(save=False, profile=False) (or membase_tools(client, save=False, profile=False)) leaves tools out; container= and k= set
defaults. Errors come back as the tool's answer (handle_tool_error=True), so the model can
tell the user, e.g. "this Membase key was narrowed or revoked".
Explicit tool calls use the SDK's own timeout (90 s): the first search after a quiet spell takes longer while the user's memory wakes.
Recall middleware (LangChain v1)
from langchain.agents import create_agent
from langchain_membase import MembaseRecallMiddleware, membase_tools
agent = create_agent(
"openai:gpt-5",
tools=membase_tools(),
system_prompt="You are a helpful assistant.",
middleware=[MembaseRecallMiddleware(k=5, timeout=8.0)],
)
Before the model call, the middleware searches Membase for the latest user message and appends the passages (each with its Memory) to the system message of that request.
- Time-boxed and fail-open: it waits at most
timeoutseconds (8 by default). On a timeout, a403or any other error it logs a warning on thelangchain_membaselogger and the model runs without memory. A search that ran past the budget finishes in the background, which wakes the memory for the next turn. - Once per user message: model calls that follow tool calls in the same turn reuse the result.
- Nothing is persisted: the recall lives in the model request only, never in the agent's state or checkpoint.
- No automatic writes by default.
capture_turns=True(opt-in) saves the user's own message(s) of each turn, once, as a note incontainer=, in the background. Assistant replies are never saved, so an AI answer is not learned as a fact about the user.
Many users
A Membase key belongs to one person, so a multi-user app holds one key (or one local
directory) per end user and passes client_for, a function from user id to that user's
client. Every class and membase_tools() accept it. The user id is read from the run:
from functools import lru_cache
from membase import Membase
from langchain_membase import MembaseRecallMiddleware, MembaseRetriever, membase_tools
@lru_cache(maxsize=1024) # reuse each user's client (and its connection pool)
def client_for(user_id: str) -> Membase:
return Membase(api_key=load_user_key(user_id)) # that user's key, never another's
retriever = MembaseRetriever(client_for=client_for)
retriever.invoke("...", config={"configurable": {"user_id": "alice"}})
agent = create_agent(
model,
tools=membase_tools(client_for=client_for),
middleware=[MembaseRecallMiddleware(client_for=client_for)],
)
agent.invoke({"messages": [...]}, config={"configurable": {"user_id": "alice"}})
The user id is taken from config["configurable"]["user_id"], or from a LangGraph run
context with a user_id field (create_agent(..., context_schema=Ctx) and
agent.invoke(..., context=Ctx(user_id="alice"))). With client_for set, a call that carries
no user id raises MissingUserIdError (the middleware logs it and skips recall); it never
falls back to another client. client_for and client/api_key/local are mutually
exclusive.
Without client_for, one client serves every call. That suits a personal agent (your own
key, your own memory), not a multi-user app.
Local mode keeps each person in their own directory:
client_for=lambda uid: Membase(local=f"/data/membase/{uid}")
(pip install 'unibaseio-membase[local]', Python 3.12+, and an LLM key such as
OPENAI_API_KEY).
Configuration
Every class takes client=Membase(...), or api_key= / base_url= / local=; otherwise
MEMBASE_API_KEY and MEMBASE_BASE_URL come from the environment. The API host defaults to
https://api.app.membase.com.
Development
uv venv .venv && uv pip install --python .venv/bin/python -e . \
'langchain>=1.1' langgraph langchain-tests pytest pytest-asyncio ruff
.venv/bin/python -m pytest # offline: the real SDK over httpx.MockTransport
MEMBASE_API_KEY=mbk_... .venv/bin/python examples/live_smoke.py # against the real API
uv build
The offline suite includes LangChain's standard tests (langchain-tests) for the retriever
and the three tools, and runs the tools and the middleware inside a real create_agent
(LangGraph) graph with a scripted chat model.
Licence
MIT
Metadata
Release files for langchain-membase 0.1.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 |
|---|---|---|---|---|
| langchain_membase-0.1.0.tar.gz | 23.1 kB | Without provenance | Details for langchain_membase-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| langchain_membase-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for langchain_membase-0.1.0-py3-none-any.whl |
Total release size: 41.7 kB
Release files / langchain_membase-0.1.0.tar.gz
| Download URL | langchain_membase-0.1.0.tar.gz |
|---|---|
| Size | 23.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f4de2aaa62cc8634247ac1910cbb039b99afff010f7bc7b95594130b5f538538
|
|
BLAKE2b-256 checksum How to use checksums |
3debbe17d4b8ba42f37d742ffb06a97cd15b0421cecc5be2cea0d359735d3d04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Release files / langchain_membase-0.1.0-py3-none-any.whl
| Download URL | langchain_membase-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c0d9d15dc0c061e0117b4343a1f1790dd5d82b75667ed4392a2a44688ffa48a
|
|
BLAKE2b-256 checksum How to use checksums |
40b5ec1126b805ff6094228346030fd835e8400ba26c97617cbb137ed4825902
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Security for langchain-membase 0.1.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.