feat(memory): Add persistent user memory system to IIAgent#193
Open
namtranii wants to merge 1 commit into
Open
feat(memory): Add persistent user memory system to IIAgent#193namtranii wants to merge 1 commit into
namtranii wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Memory Feature Architecture
1. Problem
Without memory, every conversation starts from zero. The agent cannot recall a user's name, role, preferences, or past decisions. Users must repeat context every session.
Memory solves this by:
2. System Design
2.1 Architecture Overview
2.2 Two Execution Contexts
The memory system operates in two distinct contexts that share one cache:
MemoryServicedb: AsyncSessionfrom FastAPI DIMemoryCacheServicesingletonMemoryManagerget_db_session_local()(own sessions)get_app_container().memory_service._cache(same singleton)This dual-context design is intentional: the agent runs outside the HTTP request lifecycle (Socket.IO + background tasks), but must share the same cache to avoid stale reads.
2.3 Domain Structure
3. Data Flow
3.1 Agent Message — Read Path (hot path, every message)
Design decision: On cache hit (step 6), no DB session is acquired and no Pydantic deserialization occurs. This is critical because this path runs on every single message.
3.2 Agent Message — Write Path (background, every message)
Design decision: Write path runs as a background
asyncio.Task. The agent response is not blocked by memory extraction. Cache eviction happens after each tool call, not batched, to minimize stale windows.3.3 Dashboard API — Read Path
Design decision: Paginated results use version-based cache invalidation. When memories change, the version key (
pver:{uid}) is deleted. Old page keys become orphaned and expire via TTL (300s). This avoids scanning for all page keys on eviction.3.4 User Preferences — Toggle Memory On/Off
4. Cache Design
4.1 Key Schema
All keys are namespaced by
EntityCache._make_key()→ final Redis key =memory:{key}.list:{uid}list[dict]— all memories, full schematopics:{uid}list[str]— distinct topic tagspver:{uid}str— timestamp versionpage:{uid}:{ver}:{hash}{"memories": [...], "total": int}pverevict → TTL decayprefs:{uid}{"has_memory": bool}PATCH /auth/me/preferences4.2 Cache Instance Singleton
4.3 Format Compatibility
Both
MemoryServiceandMemoryManagercan SET thelist:{uid}cache key. They MUST use identical serialization:MemoryServicereads back viaMemoryData.model_validate(d)→ Pydantic parses strings back to UUID/datetime.MemoryManagerreads back as raw dicts → accessesd["memory_id"],d["memory"]directly.5. Agent Integration
5.1 IIAgent Fields
5.2 Factory Wiring
5.3 Run Lifecycle Integration
6. Database
6.1 Table: user_memories
6.2 User Preferences
Stored in
users.metadataJSONB (no schema migration needed):{ "preferences": { "has_memory": true } }Exposed via
User.preferencesproperty andUserPublic.preferencesschema.7. API Endpoints
/v1/memories/v1/memories/topics/v1/memories/{memory_id}/v1/memories/v1/memories/{memory_id}/v1/memories/{memory_id}/v1/memories/bulk-delete/auth/me/preferenceshas_memory8. Chat Mode Integration
Chat mode (
/v1/chat) uses a completely different execution path from agent mode (Socket.IO). Memory is integrated via helper functions inchat_service.py.8.1 Architecture Difference
queryhandlerPOST /v1/chat/messagesIIAgent.aget_system_message()_abuild_memory_context()→ appends to system message_load_memory_context_for_chat()→ prepends system-role Message to context_amake_memories()viaasyncio.Taskafter model response_extract_memories_for_chat()viaasyncio.Taskafter LLM loop completesmodel_config)8.2 Read Path (Chat Mode)
The system-role message is handled differently by each provider:
convert_to_anthropic_messages()extracts system-role messages and merges intosystemparamsystem_instruction8.3 Write Path (Chat Mode)
8.4 Council Mode
Council mode (
stream_council_chat_response) also has memory integration:_load_memory_context_for_chat()prepended to messages (shared by all council models)9. Files Modified (Integration Points)
agents/agent.py_abuild_memory_context,_amake_memories, agentic tool, run lifecycleagents/factory/agent.pyhas_memory/agentic_memoryfrom tool_args, createMemoryManageragents/runs/agent.pyMemoryUpdateStartedEvent,MemoryUpdateCompletedEvent(already existed)agents/runs/events.pycreate_memory_update_started/completed_event(already existed)chat/application/chat_service.py_load_memory_context_for_chat,_extract_memories_for_chat, inject intostream_chat_response+stream_council_chat_responseusers/models.pyUser.preferencespropertyusers/schemas.pyUserPreferencesmodel, field onUserPublicusers/router.pyPATCH /auth/me/preferencesendpointcore/container.pyMemoryRepository,MemoryCacheService,MemoryServiceapp/routers.py/v1/memoriesrealtime/handlers/query.pytool_argsfrontend/src/services/memory.service.ts/v1/memories