Embedchain, or what an abandoned project teaches
Start with the most important thing, since it saves you an evening of work. Embedchain development has ended: the last release, 0.1.128, dates from March 2025, and the code is gone from the repository, which now carries the name Mem0 and solves a different problem. The old repository address redirects there.
If you are looking for a library for a new project, this is not the right choice and you can skip straight to the alternatives section. If you have working code on Embedchain, what follows describes what actually happened and what moving on looks like.
What the project did
Embedchain appeared when building search over your own documents meant assembling several layers by hand: loading files, splitting them into chunks, computing vectors, storing them in a database, and joining the query with a language model.
The proposition was simple and effective. That whole chain fit into a few lines, and the defaults were sensible, so a first working prototype took a quarter of an hour instead of two days.
from embedchain import App
app = App()
app.add("https://example.com/documentation")
app.add("./contract.pdf")
print(app.query("What is the notice period?"))The value lay in what is not visible here. Recognising the source type, loading content, splitting into chunks, choosing an embedding model, configuring a vector database, and assembling the prompt all happened without a line of configuration. For someone testing an idea rather than building infrastructure, that was a real saving.
The library handled a dozen or so source types: web pages, PDF files, text documents, video channels, code repositories, and databases. Language models and vector stores were swappable too, including the options covered in the pieces on Chroma and Qdrant.
Why the project ended
The reason lies neither in defects nor in lack of interest, but in a shift in which problem is worth solving.
Document search became a feature rather than a product. Language models gained context windows measured in hundreds of thousands of tokens, so for many uses dropping a document straight into the prompt turned out simpler than maintaining a separate vector database. Meanwhile large libraries, LangChain among them, covered the same ground and added the rest production needs.
The simplifying layer fell into a classic trap. It is convenient as long as you do exactly what the author anticipated, and once you need to change how text is split or add your own metadata filtering, the abstraction starts getting in the way. Projects of this class either grow into full frameworks or lose their reason to exist.
The authors took a third route and moved to a different problem. Instead of asking how to find a document fragment, they asked how an application should remember a conversation with a user across sessions. Mem0 came out of that, and it is a change of direction rather than another version of the same thing.
How it differs from its successor
That distinction deserves understanding, since from the outside both tools look alike: each stores text, computes vectors, and later finds something.
Document search operates over a set given in advance that changes rarely. You load documents once, and a query finds matching fragments among them. The knowledge is external to the conversation.
A memory layer operates over what the user said and changes with every utterance. You store not the whole conversation but conclusions from it: preferences, facts about the person, decisions. A language model takes part in writing, since it decides what in an utterance is worth keeping and what invalidates an earlier entry.
Three practical consequences. First, memory needs updating rather than only appending, since new information sometimes contradicts old. Second, writing costs a model call, making it more expensive than computing a vector. Third, the data belongs to a person, which brings deletion and privacy requirements that search over company documentation does not carry.
What migration looks like
There is no automatic converter here, since the interfaces differ at the level of concepts rather than function names. The scale of work depends on what you used the library for.
If you built search over a fixed document set, the successor is not the right target. Moving to one of the mature libraries, or writing that layer yourself, makes more sense, since today it amounts to a few dozen lines of code.
If you built an assistant meant to remember the user across sessions, the move makes sense and resembles writing a new layer more than translating an old one.
from mem0 import Memory
memory = Memory()
memory.add("I work in the Europe/Warsaw time zone", user_id="anna")
context = memory.search("time zone", filters={"user_id": "anna"})The biggest difference when rewriting concerns when you write something. In the old arrangement you loaded documents at startup and afterwards only asked questions. In the new one writing happens during the conversation, after each turn, and search runs before the model call to add context to the prompt.
A third route, most often skipped, is dropping the library. If you used only the basic flow, your own implementation over a vector database and a model client leaves less code to maintain than a dependency whose life cycle just ended.
What to use instead in a new project
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| Mem0 | Conversation memory, entry updates | A different problem from document search | An assistant remembering the user |
| LangChain | Broadest scope, many integrations | Heavy abstraction, many concepts | A complex flow with many steps |
| LlamaIndex | Aimed squarely at documents | Narrower reach beyond retrieval | Search over your own document set |
| Your own implementation | Zero dependencies, full control | You write everything | A simple flow, one source type |
The last row deserves more attention than it usually gets. Basic document search today means splitting text, calling an embedding model, writing to a database, and querying with the context attached. That fits into a hundred lines you understand entirely and that will not stop working when somebody archives a repository.
Choosing between the second and third rows depends on scope. If you need tools, agents, and many steps beyond retrieval, the broader library wins. If it is purely about documents, the narrower tool leaves fewer concepts to learn.
The first row solves a different problem and that is better settled before migrating, since conversation memory does not replace document search. If memory was the actual goal, though, Letta goes further in that direction, with an agent editing its own memory blocks by calling tools. It carries its own variant of the same story: in July 2026 the authors described the Python server as deprecated and maintained only for emergencies, moving development to a package installed from npm.
Your own layer in practice
Since the most common answer to a library closing is writing it yourself, it is worth showing how much work that really means.
import chromadb
from openai import OpenAI
client = OpenAI()
store = chromadb.PersistentClient(path="./data")
collection = store.get_or_create_collection("documents")
def vector(text: str) -> list[float]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=text,
)
return response.data[0].embedding
def add(chunks: list[str], source: str) -> None:
collection.add(
ids=[f"{source}-{i}" for i in range(len(chunks))],
embeddings=[vector(c) for c in chunks],
documents=chunks,
metadatas=[{"source": source} for _ in chunks],
)
def ask(question: str) -> str:
hits = collection.query(query_embeddings=[vector(question)], n_results=5)
context = "\n\n".join(hits["documents"][0])
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Answer strictly from the context."},
{"role": "user", "content": f"Context:\n{context}\n\nQuestion: {question}"},
],
)
return response.choices[0].message.contentThat is essentially the whole basic flow, leaving aside splitting text into chunks, which adds a dozen or so lines or one call to a ready library. Thirty odd lines of code instead of a dependency with an uncertain future.
The gain lies not in the line count, though, but in every decision being visible. You know which embedding model computes the vectors, how many chunks reach the prompt, and exactly how the system instruction reads. With an intermediary library those three things are hidden, and they are precisely what decides answer quality and the bill.
It also shows where the real tuning points sit. The number of returned chunks pushes cost and accuracy in opposite directions. Metadata lets you narrow search to a single source. The system instruction decides whether the model admits ignorance or fills the gap itself. None of these can be chosen in advance, since they depend on your documents.
There are two places where reaching for something ready pays off. The first is loading exotic formats, where writing your own code is not worth it. The second is flows with many steps and tools, where hand assembly starts to resemble building a framework.
One thing in this code deserves attention from the start, since fixing it later is awkward. Computing vectors one chunk at a time works with ten documents and becomes unacceptable with a thousand. Embedding models accept many texts in a single call, so batching cuts indexing time many times over and reduces the number of requests to the service. It is the simplest optimisation in this whole flow and the most often skipped, since the difference is invisible on a first prototype.
What this history teaches
Project closure is an ordinary event in this ecosystem rather than an exception, so it pays to draw conclusions for the future.
The first concerns simplifying layers. A library whose entire value lies in shortening fifteen lines to three is fragile by design. Once the underlying task becomes simpler or a larger project absorbs it, the layer loses its reason to exist. That is not a charge against the authors but a predictable property of such tools.
The second concerns dependence on abstractions. Code calling a vector database and a language model directly survives the closure of any intermediary project. Code built around somebody else's central object needs rewriting.
The third concerns judging a dependency before adding it. Check the date of the last change, the number of people with write access, and whether a company stands behind the project, and if so, whether the project is that company or merely its side release. Answers to those questions say more than a star count.
The fourth is optimistic. Archived code does not vanish, so a working project on pinned versions will keep working. The risk concerns security fixes and compatibility with new dependency versions rather than a library suddenly disappearing.
If you run such a deployment in production, three steps are enough to stop worrying about it. Pin exact versions of every dependency, transitive ones included, since those most often break an unmaintained project. Write a note in the repository stating plainly that the library is no longer maintained and why it is still there, since nobody will remember a year from now. Isolate the library calls behind your own interface covering two operations: adding a document and querying. That third step takes an afternoon and turns a future migration from rewriting the application into swapping one file.
It also pays to set a reminder to check security advisories for that dependency every few months. An unmaintained project will not get a fix, but knowing about a problem lets you act earlier than the day somebody exploits it.
Common mistakes
The first is starting a new project on an abandoned library because it ranked high in search. The date of the last release says more than a position in results.
The second is treating the successor as a backward compatible version. It is a tool for a different problem, so migration is rewriting a layer rather than swapping imports.
The third is moving document search into a memory layer. Storing static documentation through a mechanism designed for changing facts about a user costs model calls and returns nothing.
The fourth is leaving dependency versions unpinned. On an unmaintained project every update to a neighbouring library can break it, and nobody will fix that.
The fifth is migrating everything at once. Isolating search access behind your own interface first, then swapping the implementation underneath, makes more sense.
The sixth is skipping the question of whether the library is needed at all. With one source type and a simple flow, your own code is often shorter than configuring somebody else's.
FAQ
Does Embedchain still work?
Yes, the package still sits in the PyPI registry, installs, and runs, and existing projects on pinned versions keep working. Development stopped at release 0.1.128 in March 2025, though, and the code is gone from the repository, so no fixes or support for new dependency versions are coming.
Is Mem0 a new version of Embedchain?
Not in the compatibility sense. Mem0 grew from the same project and the same organisation, while solving a different problem: remembering a conversation with a user rather than searching a fixed document set. The interfaces differ, so moving over means writing a new layer.
What replaces it for document search?
It depends on scope. For a simple flow your own implementation over a vector database is around a hundred lines with no dependency at all. For a complex flow, mature libraries such as LangChain or LlamaIndex make more sense.
Is migrating worth it if everything works?
Not immediately. A working system on pinned versions can be left alone and the move planned calmly. It does pay to isolate search access behind your own interface, since that turns a future migration from rewriting the application into swapping one module.
How do I tell whether a library has a future?
Look at the date of the last release, the number of people with write access, and the pace of issue closure. Check too whether the project is a company's main product or a side release, since the latter disappears more often. A star count says nothing about maintenance.
The project's history is documented in the Mem0 repository, and the successor's documentation sits on the project site.