Skip to content

[Enhancement] Per-user long-term memory and conversation summarization for personalized answers #41

Description

@zeemscript

Summary

The assistant has amnesia. It has no concept of a user — only anonymous chat_id sessions — so a student who has spent weeks asking about tajweed at a beginner level starts from zero in every new conversation, and a long conversation loses its earliest (often most important) context the moment any history cap lands. This issue adds a personalization memory layer: an optional user_id on chat requests, a per-user long-term memory (knowledge level, madhhab preference, preferred language, topics studied, standing facts the user shared) that is extracted from conversations and injected into future sessions, and summarization-based compaction that folds old turns into a running summary instead of discarding them. This is what turns a stateless Q&A endpoint into a tutor that knows its student.

Current state

  • There is no user identity anywhere in the service: ChatRequest (main.py, lines 79–82) carries prompt, chat_id, and context only. Two conversations by the same person are unrelatable.
  • Sessions live in the in-process active_chats dict (line 54), keyed by chat_id, and are wiped on every restart. Issue [Enhancement] Persist chat sessions instead of holding them in process memory #3 covers moving session history to an external store (Redis); it does not add user identity or any cross-session memory.
  • Issue [Enhancement] Cap conversation history with a token budget to prevent unbounded prompt growth #13 covers capping history with a token budget by dropping oldest turn pairs; its body names summarization-based compaction an optional stretch goal and explicitly does not require it. Nothing anywhere extracts durable facts about the user.
  • The DeenBridge backend (dnb-backend) has authenticated users, so the frontend can supply a stable user identifier — the AI service just never asks for one.

What to build

  1. user_id on requests — add user_id: Optional[str] to ChatRequest. No user_id → exactly today's stateless behavior; memory features activate only when it is present. Treat it as an opaque string (auth/API-key work is issue [Enhancement] Add service API-key auth and rate limiting to protect the Gemini quota #9's scope, not this one).
  2. Memory store — a memory.py module behind a small interface with two backends: the same Redis the session-persistence issue ([Enhancement] Persist chat sessions instead of holding them in process memory #3) targets (REDIS_URL), and an in-memory/dict fallback for local dev and tests. Schema: a structured profile (knowledge_level, madhhab, language, topics_studied with recency, plus a bounded list of free-form remembered facts like "is a new convert", "studying for an exam"), each entry carrying a timestamp. Cap the profile size (e.g. max N facts, LRU/oldest-out) so it cannot grow unboundedly.
  3. Memory extraction — after a conversation turn completes (or every K turns — justify the trigger), run a cheap structured-output Gemini call over the recent exchange that proposes profile updates ("user said they follow the Maliki school", "asked third question about inheritance — add topic"). Apply updates through validation (e.g. madhhab must be a known value — reuse normalization from the madhhab-awareness issue if both land). Run extraction outside the request's critical path (background task via FastAPI's BackgroundTasks or equivalent) so chat latency is unaffected.
  4. Memory injection — when a session starts (or on each request for stateless-per-request designs), render the profile into a compact, clearly delimited context block ("Known about this student: …") appended to the system context — never into the user turn, and written so it composes with the injection-hardening work in [Bug] Islamic system prompt is skipped for plain requests and easily overridden by prompt injection #5 (data, not instructions).
  5. Summarization compaction — implement the compaction [Enhancement] Cap conversation history with a token budget to prevent unbounded prompt growth #13 left as a stretch goal: when a conversation exceeds the history budget, summarize the turns being evicted into a running conversation summary (one model call, structured to preserve open questions, established facts, and the user's goal) and keep that summary pinned at the head of the context. Coordinate directly with [Enhancement] Cap conversation history with a token budget to prevent unbounded prompt growth #13's assignee if both are in flight — this issue owns "summarize", that one owns "budget + truncate", and the seam is the eviction hook.
  6. Privacy controls — memory is personal data about a person's religious life; treat it accordingly: GET /memory/{user_id} returns the stored profile (transparency), DELETE /memory/{user_id} erases it completely, a remember: bool = True request flag lets a caller mark a conversation as do-not-learn, and profile entries carry TTL (env-configurable, e.g. MEMORY_TTL_DAYS). Never log profile contents at INFO level.
  7. Tests — extraction-application logic against a fake store and mocked model (proposed updates validated, capped, timestamped); injection rendering; compaction seam (summary replaces evicted turns and is preserved thereafter); privacy endpoints (get/delete/opt-out). All offline; CI stays green.

Acceptance criteria

  • Requests without user_id behave byte-for-byte like today; with user_id, a fact established in one conversation (e.g. "I'm a beginner") measurably shapes a new conversation's answer (manually demonstrated in the PR, plus injected-context unit assertions).
  • Memory extraction runs off the critical path — /chat latency shows no added model call in the request/response timeline.
  • The profile is bounded (size cap + TTL) and every entry is timestamped; validation rejects malformed extraction proposals.
  • A conversation driven past the history budget retains earlier established facts via the pinned summary — the model correctly answers a question about something said before the eviction point.
  • GET/DELETE memory endpoints work; remember: false conversations leave no trace; profile contents never appear in INFO logs.
  • Redis and in-memory backends pass the same test suite; store selection via REDIS_URL presence is documented in the README env-var table.
  • CI stays green with new modules linted and tested.

Pointers

Difficulty

High — background extraction with validation, a two-backend store, compaction that must interlock cleanly with a separately-assigned truncation issue, personalization that respects prompt-injection boundaries, and privacy endpoints — all with a hard requirement that anonymous traffic is completely unaffected.


🏆 GrantFox OSS — Official Campaign | FWC26. Apply for this issue through the GrantFox campaign page. The maintainer assigns one contributor before work starts; unassigned PRs may not be reviewed. PRs target the dev branch. Quality bar: CI must stay green.

💬 Questions or need help? Reach the maintainers and other contributors on the DeenBridge Telegram: https://t.me/+nst9lXNj1wc4ZDE0

Metadata

Metadata

Assignees

Labels

GrantFox OSSPart of the GrantFox OSS programMaybe RewardedPotential reward for completionOfficial Campaign | FWC26GrantFox official campaign FWC26complexity:highMaps to Drips Wave High tier (200 pts)enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions