Inspiration
I've been annoyed by this problem for a while. Every AI coding assistant I use forgets everything the moment the session ends. You explain your preferred patterns, your architectural decisions, the bugs you spent hours tracking down, and next session you're starting from zero again. It's like working with a colleague who has amnesia.
Human collaborators don't work that way. A good colleague remembers that you prefer composition over inheritance, that the auth service uses a non-standard JWT format, that you've already ruled out approach X for a specific reason. They don't remember everything forever either. Irrelevant details fade, but the things that matter stay sharp.
I wanted to build something that works the same way. An assistant that actually builds a model of you across sessions, on its own, without you having to say "remember this" every five minutes.
The idea clicked when I came across Ebbinghaus's forgetting curve. Hermann Ebbinghaus was a 19th century psychologist who figured out that memory retention follows an exponential decay, and that repeated retrieval actively fights that decay. That felt like exactly the right mental model. If that's how human memory works, why not use it as the foundation for machine memory too?
What it does
DevMemory Agent is an AI developer assistant that remembers you across sessions. You chat with it like any other assistant, but it picks up on your patterns over time: your tool preferences, your architectural decisions, recurring bugs you've hit before, coding conventions you follow. No explicit "remember this" needed. It figures it out from the conversation.
Every response is augmented with memories pulled from past sessions. After each response, it quietly extracts new facts from what you wrote and stores them. Memories you keep using stay relevant. Memories you never come back to slowly fade and eventually disappear.
It has a REST API and an MCP server, so it plugs into Claude Code or Claude Desktop directly. There's also a Next.js demo UI with a live memory panel where you can see each memory's importance score and watch it decay in real time between visits.
How we built it
Every message goes through five stages:
Retrieve - the message gets embedded with Qwen Cloud's text-embedding-v4 and matched against stored memories in PostgreSQL using the pgvector extension and cosine similarity. Top 20 candidates come back.
Rerank - qwen3-rerank reorders those candidates by actual semantic relevance. Decay is applied here too, so old memories rank lower even if they're textually close to the query.
Fit - a greedy algorithm packs the most valuable memories into an 8,000-token budget and injects them into the system prompt.
Respond - qwen3.7-max generates a response with that context. It also has four tools it can call mid-conversation: recall context, save an insight, find similar past problems, and check memory health.
Remember - after the response is sent, a background task extracts new facts from what the user wrote (never the assistant's reply, to avoid re-saving things it just recalled). Near-duplicate extractions reinforce the existing memory instead of creating a new row. Memories that were actually retrieved get a +0.2 importance boost.
The decay math is based on Ebbinghaus's forgetting curve with an access-count bonus:
$$\text{effective_decay} = \max!\left(0,\; \lambda - \ln(1 + n) \cdot 0.1\right)$$
$$I(t) = I_0 \cdot e^{-\text{effective_decay} \cdot \Delta t}$$
where $I_0$ is the base importance, $\lambda$ is the per-type decay rate, $n$ is how many times the memory has been accessed, and $\Delta t$ is days since last access. Different memory types decay at different rates:
| Type | $\lambda$ |
|---|---|
preference |
0.02 (slowest) |
pattern |
0.03 |
decision |
0.05 |
bug_fix |
0.08 |
general |
0.10 (fastest) |
Memories that drop below 0.05 importance get auto-deleted.
The stack: FastAPI backend on Python 3.11, Next.js 14 frontend with TypeScript and Tailwind, PostgreSQL 16 with pgvector on Alibaba Cloud RDS, Redis for session caching, and Docker Compose tying together five services on an Alibaba Cloud ECS instance. The MCP server runs on port 8001 via fastmcp, so any MCP-compatible tool can connect to the same memory store with one command.
Challenges we ran into
Silent failure from wrong embedding dimensions. I initialized the pgvector column as vector(1536) based on what OpenAI's embedding models return. text-embedding-v4 returns 1024 dimensions. The mismatch didn't throw an error at insertion time because SQLAlchemy was silently truncating the vector, so rows stored fine but cosine similarity queries returned nonsense scores. There was no obvious failure point to trace. I only caught it by logging the raw embedding output and comparing its length against the column definition.
The OpenAI-compatible endpoint doesn't expose the reranker. Qwen Cloud provides an OpenAI-compatible base URL that works for chat completions and embeddings, which made me assume the reranker would be accessible through the same interface. It isn't. The reranker is only available through DashScope's native API with a different authentication header and a completely different request schema. The compat layer accepted the request without error and returned an empty candidates list, which looked like a retrieval problem, not a client configuration problem. That cost me a few hours of debugging the wrong layer.
Memory recursion from extracting assistant replies. The extraction pipeline ran on the full conversation turn, including the assistant's response. When the agent recalled a memory and repeated it in its reply, the extractor would pick that up as a new fact and insert a near-duplicate row. Over a long session the memory store would accumulate multiple slightly-reworded versions of the same thing, each with a fresh timestamp, which effectively reset the decay clock and kept noise alive. The fix was straightforward once I identified it: extraction runs exclusively on the user's message, never on the assistant's output.
pgvector cosine search and the decay score interaction. The initial retrieval stage returns the top-N candidates purely by embedding similarity, with no awareness of how stale a memory is. A memory from six months ago that happens to be textually close to the current query would rank above a recent, highly relevant one. Applying decay at the retrieval stage means reordering by a combined score of semantic similarity and current importance, but that requires fetching more candidates than you need (we use 20) and reranking with the LLM-based reranker before selecting what goes into the context window. Getting that two-stage pipeline to behave predictably across different query types took several iterations of threshold tuning.
Synchronous extraction blocking response latency. Memory extraction involves an LLM call to classify and structure facts from the conversation turn. Running that inline before returning the response added 1.2 to 1.8 seconds to every reply, which made the agent feel sluggish regardless of how fast the actual reasoning step was. FastAPI's BackgroundTasks mechanism lets you fire a coroutine after the response has been sent to the client, so the fix was moving the entire extraction pipeline there. The tricky part was that the background task needs the same database session and embedding service as the main request handler, so wiring the dependency injection correctly through dependency-injector required some restructuring of the container bindings.
Python version conflict from an unused transitive dependency. The backend Docker image was python:3.11-slim. At some point during development a package I was evaluating pulled in numpy==2.5.0 as a transitive dependency. NumPy 2.5.0 dropped support for Python 3.11 and the image build started failing with a wheel compilation error. The error message pointed at NumPy but not at what had introduced it, so I had to walk the dependency tree manually with pip-tree to find the culprit. The package wasn't being used anywhere in the codebase, so removing it from requirements.txt was the right call rather than pinning a lower NumPy version.
Accomplishments that we're proud of
The decay model actually working the way I imagined it is probably what I'm most happy about. Seeing the live memory panel in the frontend with bars visibly shrinking between visits, using the same Ebbinghaus formula running client-side, made the whole thing feel real. It's not just a technical detail, it's visible behavior that makes sense to a user.
The autonomous extraction pipeline came out cleaner than I expected. The near-duplicate detection before inserting means the memory store stays compact instead of filling up with slightly rephrased versions of the same fact.
The MCP integration is also something I'm really glad I built. One claude mcp add command and Claude Code is reading from and writing to the same persistent memory store. Memories from a chat session show up in the IDE automatically.
What we learned
Ebbinghaus's forgetting curve is actually a practical engineering tool, not just a psychology concept. Formalizing memory as a time-decaying score with access-count reinforcement gave the system a principled way to decide what stays and what goes. Much cleaner than TTL-based expiration or hard storage limits.
The two-stage retrieve-then-rerank pattern is significantly better than vector search alone. The reranker surfaces memories that cosine similarity would have buried, especially for short or abstract queries where the embedding space doesn't capture intent well.
Running extraction as a background task post-response is the right call for any LLM side-effect that doesn't block the user. The latency improvement was immediate and the difference in how it felt to use was obvious.
What's next for DevMemory Agent
The multi-tenant data model already has project_id and workspace_id columns. The next step is surfacing those properly so teams can have shared memory alongside personal memory.
A VS Code extension that surfaces relevant memories inline while you type would make this much more practical day-to-day without requiring a separate chat window.
The extractor is currently a single LLM pass. A richer taxonomy and better deduplication using the reranker rather than a similarity threshold would improve memory quality a lot over time.
And eventually, letting users tune decay rates for their own patterns instead of using global constants. Some people want the agent to hold onto things longer. Some want faster forgetting. That should be configurable.
Built With
- alibaba-cloud-ecs
- alibaba-cloud-rds
- dependency-injector
- docker
- fastapi
- fastmcp
- mcp-(model-context-protocol)
- next.js
- pgvector
- postgresql
- python
- qwen-cloud-api
- qwen3-rerank
- qwen3.7-max
- recharts
- redis
- tailwind-css
- text-embedding-v4
- typescript