Track 1: MemoryAgent — Global AI Hackathon with Qwen Cloud
A persistent memory layer where Qwen actively scores, recalls, and forgets memories across sessions. Unlike stateless chat, MemoryAgent remembers what users tell it — preferences, goals, project details, personal context — and injects that memory into every response, even days later.
Most AI chat is stateless. Every session starts from zero. MemoryAgent fixes that.
Every conversation turn runs through a full memory pipeline:
- Recall — semantic search finds relevant past memories for the current query
- Retrieve — recalled memories are formatted into a context block ordered by importance and relevance
- Extract — after responding, Qwen extracts key facts from the turn as structured memories
- Score — each memory gets an importance score (0.0–1.0) from Qwen
- TTL — low-importance memories expire in 24h, medium in 7 days, high-importance are permanent
- Forget — a smart forget endpoint lets Qwen review and delete truly useless memories
The result: an agent that gets more useful over time, not less.
┌─────────────────────────────────────────────────────────────┐
│ User (Browser) │
│ frontend/index.html (port 8001/app) │
└────────────────────────┬────────────────────────────────────┘
│ HTTP
▼
┌─────────────────────────────────────────────────────────────┐
│ agent.py (port 8001) │
│ POST /chat → recall → Qwen reply → bg extract/store │
│ GET /chat/memories · POST /auth/register · /auth/login │
│ (all except /auth/* and /health require a Bearer token) │
└──────────┬──────────────────────────────┬───────────────────┘
│ HTTP │ OpenAI-compat SDK
▼ ▼
┌──────────────────────┐ ┌───────────────────────────────┐
│ memory_api.py │ │ Qwen Cloud API │
│ (port 8000) │ │ qwen-plus (chat + scoring) │
│ │ │ text-embedding-v3 (1024-dim) │
│ POST /memory │ └───────────────────────────────┘
│ POST /recall │
│ DELETE /memory/{id} │
│ DELETE /memories │
│ DELETE /forget │
│ GET /memories │
│ POST /auth/register │
│ POST /auth/login │
└──────┬───────────────┘
│ │
▼ ▼
┌─────────────┐ ┌───────────────┐
│ Neon DB │ │ Upstash Redis │
│ Postgres │ │ (cache 1hr) │
│ + pgvector │ └───────────────┘
│ 1024-dim │
└─────────────┘
| Layer | Technology |
|---|---|
| LLM + Scoring | Qwen Cloud (qwen-plus) |
| Embeddings | Qwen Cloud (text-embedding-v3, 1024-dim) |
| Vector DB | Neon PostgreSQL + pgvector |
| Cache | Upstash Redis |
| Auth | JWT (PyJWT) + bcrypt password hashing |
| Backend | FastAPI + asyncpg |
| Frontend | Vanilla HTML/CSS/JS |
| Deploy | Alibaba Cloud ECS |
Importance scoring — Qwen rates every memory 0.0–1.0 based on content type:
>= 0.6→ permanent (goals, preferences, key facts)0.3–0.6→ expires in 7 days< 0.3→ expires in 24 hours (greetings, filler)
The scoring prompt is explicitly calibrated against two failure modes found during testing:
- Specific beats vague — a named, concrete fact (e.g. "grows cherry tomatoes and basil") must score at least as high as the general category it belongs to (e.g. "is an indoor gardener"). Without this rule, vague statements were outscoring the specific facts that actually make recall useful.
- Names have a floor — a person's own name is treated as a 0.6+ identity fact, never scored as casual/trivial, so it can't accidentally expire within a day of being mentioned.
Importance-first recall — pgvector cosine similarity, ranked ORDER BY importance_score DESC, similarity DESC. This guarantees a user's core profile facts (name, project, deadlines) surface on the very first turn of a new session, even before enough conversation exists for a strong semantic match.
Weighted duplicate/conflict detection — before storing, candidate memories are ranked by a blended score (60% similarity + 40% importance) and checked against the new content:
- similarity > 0.96 or exact text match → rejected as duplicate (
409) - similarity > 0.82 → Qwen arbitrates: does the new fact
UPDATE(correct/supersede) the old one, or is it aNEWindependent fact? IfUPDATE, the old row is overwritten in place rather than creating a redundant entry.
Negative-fact filtering — the extraction prompt explicitly forbids generating memories from absence-of-information statements (e.g. "user doesn't have a car"), preventing the memory store from filling with noise.
Structured context retrieval — recalled memories aren't dumped into the prompt as raw rows; they're formatted into a clean, dated bullet list ordered by importance and relevance before being injected into the system prompt.
Smart extraction — after each turn, a second Qwen call extracts structured facts from the conversation (up to 3 per turn) rather than storing raw message text. This runs as a background task after the chat reply is already sent, not before it — the user isn't kept waiting on memory_api.py's embed/dedup/arbitrate/score chain for facts that aren't needed to answer them. POST /chat returns extraction_pending: true and an empty memories_stored accordingly; the memory panel picks up newly stored facts on its next refresh rather than from the chat response itself.
Smart forget — memories with expires_at in the past (low/medium importance only — permanent memories with expires_at = NULL are never touched) are batch-reviewed via DELETE /forget. For each candidate, Qwen weighs the content, its original importance score, and its age, then votes DELETE or KEEP. Deleted memories are hard-removed from Neon and purged from the Redis cache; kept memories get their TTL renewed by 7 days rather than being re-flagged every cycle.
Prompt injection hardening — since 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. Both the system prompt (recall) and the extraction prompt (storage) explicitly frame all user/memory content as untrusted data to read, never instructions to follow — including text that impersonates system/admin commands. The conflict-arbitration prompt has the same framing, so a crafted memory can't manipulate the UPDATE/NEW verdict into overwriting unrelated memories. This is prompt-level defense-in-depth, not a hard guarantee — LLM-based defenses reduce but don't eliminate injection risk.
qwen-memory-agent/
├── memory_api.py # Core memory CRUD, scoring, recall, forget
├── agent.py # Chat layer — memory injection + extraction
├── seed_memories.py # Test data seeder
├── test_memory_agent.py # End-to-end test suite (API + frontend file validation)
├── frontend/
│ └── index.html # Login/register + chat UI + live memory panel + intro/how-to-use guide
├── .env.example
├── requirements.txt
└── README.md
git clone https://github.com/Hereforlolz/qwen-memory-agent
cd qwen-memory-agent
python -m venv venv
venv\Scripts\activate # Windows
pip install -r requirements.txtcp .env.example .envFill in .env:
QWEN_API_KEY=sk-...
QWEN_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
QWEN_MODEL=qwen-plus
QWEN_EMBEDDING_MODEL=text-embedding-v3
DATABASE_URL=postgresql://...neon.tech/neondb?sslmode=require
REDIS_URL=rediss://default:...@...upstash.io:6379
MEMORY_API_URL=http://localhost:8000
JWT_SECRET=a-long-random-string
JWT_EXPIRE_HOURS=24
ALLOWED_ORIGINS=http://localhost:8001
Note: Upstash requires
rediss://(double s) for TLS. Neon requires?sslmode=require.JWT_SECRETmust be set to the same value in bothmemory_api.py's andagent.py's.env— they verify tokens independently rather than one delegating to the other, so a mismatch fails every token check.
Terminal 1 — memory API:
python memory_api.py
# running on http://localhost:8000Terminal 2 — chat agent + frontend:
python agent.py
# API on http://localhost:8001
# Frontend at http://localhost:8001/appRegisters (or logs in as) nidhi first, then seeds a few memories under that account.
SEED_USER_PASSWORD=your-choice python seed_memories.pyOmitting
SEED_USER_PASSWORDfalls back to a hardcoded demo password, with a warning — fine for local testing, not for a shared/public deployment.
Every endpoint below except /auth/* and /health requires Authorization: Bearer <token>. There's no user_id in any request body or path anymore — identity comes from the token alone.
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/register |
Create an account (username + password) — returns {access_token, token_type, username} |
| POST | /auth/login |
Authenticate an existing account — same response shape |
| POST | /memory |
Store a memory — Qwen scores + embeds |
| POST | /recall |
Semantic search + context synthesis |
| GET | /memories |
List the authenticated user's memories, sorted by importance |
| DELETE | /memory/{memory_id} |
Hard delete a single memory — scoped to rows the authenticated user owns |
| DELETE | /memories |
Delete all of the authenticated user's memories |
| DELETE | /forget |
Qwen-arbitrated smart forget — reviews expired, low-importance memories and deletes or renews each (body: {batch_size}) |
| GET | /health |
Health check — DB must be reachable; Redis is reported (redis: "ok"/"unreachable"/"disabled") but never fails the check |
Same auth requirement as above; each protected endpoint verifies the token itself and forwards it to memory_api.py.
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/register |
Proxies to memory_api.py |
| POST | /auth/login |
Proxies to memory_api.py |
| POST | /chat |
Full memory-injected chat turn — returns the reply immediately; memories_stored is always [] and extraction_pending: true signals extraction/storage is running in the background |
| GET | /chat/memories |
Memory panel data for frontend |
| DELETE | /memory/{memory_id} |
Proxies to memory_api.py — delete a single memory |
| DELETE | /memories |
Proxies to memory_api.py — delete all of the authenticated user's memories |
| DELETE | /forget |
Proxies to memory_api.py's smart forget |
| GET | /health |
Health check |
Open http://localhost:8001/app after starting agent.py.
- Login / Register screen — shown whenever no valid token is stored; toggles between the two modes. The token is kept in
localStorageand attached to every request asAuthorization: Bearer <token>; a401response logs the session out automatically. - Chat panel — standard chat, with badges showing how many memories were recalled and whether the turn was stored
- Memory panel — live view of all stored memories, sorted by importance score, with color-coded TTL bars
- 🧹 Smart Forget button — manually triggers a review of expired memories for the logged-in user via
DELETE /forget, and shows the reviewed/deleted/kept counts inline - New Session — starts a fresh session ID while keeping all memories intact (tests cross-session recall); unrelated to login — it doesn't sign you out
- Clear Chat — wipes the UI conversation history, memory unaffected
- 🗑 per-card delete — remove individual memories
- ✕ Clear All — nuke all memories for the logged-in user
- Logout — clears the stored token and returns to the login screen
- "How this works" intro panel — shown on first load, walks new users/judges through what the app does and a step-by-step script to test cross-session recall in under a minute. Dismissible via the close button, reopenable via the header link.
- Register a new account (any username/password), tell the agent your name, your project, your preferences
- Click New Session (or restart the server entirely) — this does not log you out
- Ask the agent something related — it will recall and reference what you told it
- The memory panel shows which memories were injected into that response
test_memory_agent.py is a real end-to-end suite — it makes live HTTP calls against a running instance (local or deployed) rather than mocking anything, so a passing run is genuine proof the system behaves as documented.
# against local servers (memory_api.py on :8000, agent.py on :8001)
python test_memory_agent.py
# against the live Alibaba Cloud deployment
python test_memory_agent.py --remote <ECS-public-IP>Registers a fresh test account first (POST /auth/register, via agent.py's proxy) and authenticates every subsequent call with the returned token. Covers: health checks, store/recall, importance scoring calibration (including the specific-vs-vague and name-floor rules above), deduplication and conflict arbitration, negative-fact filtering, cross-session recall, smart forget, manual delete, and a structural validation pass over frontend/index.html (catches regressions like a hardcoded API_BASE, a leaked default credential in the login form, an authenticated call that bypasses the authFetch() wrapper, or a dynamic value interpolated back into an inline onclick handler — before any of them reach a live deployment).
- Qwen Cloud / DashScope — LLM + embeddings
- Neon — serverless Postgres with pgvector
- Upstash — serverless Redis
- Alibaba Cloud ECS — deployment
##Link to demo DEMO: https://vimeo.com/1204609875?fl=tl&fe=ec
MIT