-
-
The in-app guide judges see on load: what it does, and a 60-second script to test cross-session recall themselves.
-
Brand-new session, zero chat history, yet it recalls name, role, and deadline. The recalled-memories badge proves it's not guessing.
-
Qwen scores every memory 0.0-1.0 live. Names and deadlines land permanent; vague facts expire faster than specific ones.
-
Smart Forget reviewing expired memories one by one. Qwen decides delete-or-keep per memory instead of wiping by timestamp alone.
Inspiration
Most AI chat is stateless. Every conversation starts from zero, so you re-explain your context, your preferences, your ongoing projects, every single time. That's not how a useful assistant should work, and it's not how the relationships we have with people who know us well actually function. Track 1 asked for an agent that "autonomously accumulates experience" and "makes increasingly accurate decisions across multi-turn, cross-session interactions." I assumed that meant storage and retrieval. Turns out the actual problem was buried one level deeper: knowing when a new memory should replace an old one instead of just piling up next to it.
If a user tells an agent "I prefer Python" today and "I've switched to Rust" three weeks later, a naive memory system ends up with two permanently contradictory facts. Get this wrong and the agent either forgets things it shouldn't, or remembers things that are no longer true. That's the actual problem this project is about.
What it does
Qwen MemoryAgent is a persistent memory layer for conversational AI. Every chat turn runs through a full pipeline:
- Recall: semantic search over the user's memory history finds what's relevant to the current message
- Retrieve: recalled memories are formatted into a clean, dated context block ordered by importance and relevance, rather than dumping raw memory rows into the prompt
- Respond: the chat reply is generated with that context injected into the system prompt
- Extract: a second Qwen call pulls structured facts out of the turn, including resolving references like "save this" or "remember that plan" back to whatever was actually discussed earlier in the conversation
- Score: each new fact gets a 0.0 to 1.0 importance score from Qwen, which determines whether it's kept for 24 hours, 7 days, or permanently
- Deduplicate and arbitrate conflicts: before writing anything, candidate memories are ranked by a blended similarity plus importance score. Near-duplicates are rejected outright, and close matches are handed to Qwen to decide whether the new fact corrects an old one (overwrite) or stands independently (keep both)
- Forget: a Smart Forget pass reviews memories that have already expired and asks Qwen to make a final keep-or-delete call on each one, rather than deleting blindly by timestamp
The result is an agent that gets more useful the longer you use it, not less. Ask it what you're working on after closing the tab and coming back days later, and it actually knows.
How we built it
Stack: FastAPI (two services, a chat orchestration layer and a standalone memory core), Qwen Cloud (qwen-plus for chat, scoring, and arbitration, text-embedding-v3 for 1024-dim embeddings), Neon PostgreSQL with pgvector for vector storage, Upstash Redis for caching, deployed on Alibaba Cloud ECS, vanilla HTML/CSS/JS frontend.
Two services instead of one. The memory core (memory_api.py) is a standalone, reusable layer with its own API contract. The chat layer (agent.py) is a thin orchestrator on top. This means the memory system could plug into a different agent without modification, and each half is independently testable, which mattered a lot once the project had a 44-check automated test suite running against the live deployment.
Importance-first recall, not pure similarity. Memories are ranked ORDER BY importance_score DESC, similarity DESC rather than pure cosine similarity. That's deliberate: a user's name, current project, or deadline needs to surface on the very first turn of a brand-new session, before there's enough conversation for a strong semantic match to even exist.
Weighted conflict arbitration is the core engineering piece. Candidate memories are scored by 60% similarity plus 40% importance. Above a 0.96 similarity threshold, or an exact text match, a new memory is rejected outright as a duplicate. Between 0.82 and 0.96, Qwen is asked to arbitrate: does this new fact correct the old one, or are both independently true? The answer determines whether the database does an UPDATE on the existing row or an INSERT of a new one, so a user's preferences can genuinely evolve over time without the memory store filling up with stale, contradictory facts.
Prompt injection hardening. Because stored memories get re-injected into the system prompt of future, unrelated sessions, a malicious message stored as a memory could otherwise function as a persistent, cross-session jailbreak. Every prompt that touches memory content (recall, extraction, and conflict arbitration) explicitly frames that content as untrusted data to read, never instructions to obey, including text that impersonates system or admin commands.
Challenges we ran into
The most instructive bugs weren't in the obvious places.
Importance scoring initially penalized the facts that mattered most. Early testing showed a name being scored low enough to expire within 24 hours, and a specific fact ("grows cherry tomatoes and basil") scoring lower than the vague category it belonged to ("is an indoor gardener"). That's exactly backwards, since specificity is what makes recall useful. Fixed by adding explicit calibration anchors and a floor rule for identity facts directly into the scoring prompt.
"Save this" silently did nothing. Asking the agent to save something it had said earlier, rather than stating a new fact directly, produced a confident-sounding confirmation reply with no actual memory behind it. The extraction step only ever saw the current turn's two messages, never the conversation history needed to resolve what "this" referred to. Fixed by feeding recent history into the extraction prompt specifically for reference resolution.
Smart Forget silently never forgot anything. Every single test run showed 0 deleted. It looked like cautious behavior, but it was actually a crash. datetime.utcnow() (timezone-naive) was being subtracted from database timestamps (timezone-aware), throwing a TypeError that was silently caught and defaulted to "keep." The feature had never actually run once until this was found and fixed.
All three were caught by building a real end-to-end test suite that makes live HTTP calls against the deployed instance rather than mocking anything, and by manually forcing a memory into an expired state in the database to prove Smart Forget could actually delete something, not just respond with a 200.
One thing I'll say plainly instead of glossing over: conflict arbitration isn't fully deterministic, because it's a live Qwen call, not a fixed rule. The same near-duplicate pair has produced both an UPDATE and a NEW verdict across different test runs, depending on small differences in phrasing. That's not a bug so much as a real constraint of using an LLM as the judge instead of hardcoding the logic.
What's next
Scheduled (rather than on-demand) Smart Forget, row-level locking for conflict arbitration to handle concurrent writes safely at scale, and extending the dedup/arbitration logic to catch paraphrased near-duplicates that currently slip past the similarity threshold.
Built With
- alibaba-cloud-ecs
- fastapi
- javascript
- neon-postgresql
- pgvector
- python
- qwen-cloud
- qwen-plus
- text-embedding-v3
- upstash-redis
Log in or sign up for Devpost to join the conversation.