Skip to content

feat: Memories/Personalization#5269

Open
shatfield4 wants to merge 41 commits intomasterfrom
feat/memory
Open

feat: Memories/Personalization#5269
shatfield4 wants to merge 41 commits intomasterfrom
feat/memory

Conversation

@shatfield4
Copy link
Copy Markdown
Collaborator

@shatfield4 shatfield4 commented Mar 26, 2026

Pull Request Type

  • ✨ feat (New feature)
  • 🐛 fix (Bug fix)
  • ♻️ refactor (Code refactoring without changing behavior)
  • 💄 style (UI style changes)
  • 🔨 chore (Build, CI, maintenance)
  • 📝 docs (Documentation updates)

Relevant Issues

resolves #5276

Translations PR: #5277

Description

  • Added a new memories database table with Prisma schema and migration for storing user memories scoped to global or per-workspace
  • Added a Memory server model with full CRUD, promote-to-global, replace workspace memories, and multi-user migration support
  • Added memoryProcessed flag to workspace_chats table to track which chats have been processed for memory extraction
  • Added server API endpoints for memories (list, create, update, delete, promote to global, clear all, run extraction)
  • Added a memoryEnabled middleware guard on all memory endpoints so they 403 when the feature is off
  • Added ownership checks on memory update/delete/promote so users can only modify their own memories
  • Added a background worker job (extract-memories) that runs every 15 minutes, uses the workspace LLM to extract memories from unprocessed chats
  • Background extraction skips running if the user has been active in the last 20 minutes to avoid processing mid-conversation
  • Added memory injection into chat system prompts (streaming, sync, and API chat handlers) via a shared promptWithMemories util
  • Memories are reranked against the current prompt + recent history using the native embedding reranker when more than 5 workspace memories exist
  • Added global memory limit of 5 and workspace memory limit of 20, enforced on create and promote
  • Added admin Personalization settings page with toggle to enable/disable the feature, run extraction manually, clear all memories, and manage global memories
  • Added workspace-level Personalization settings tab where users can view, add, edit, delete, and promote workspace memories to global
  • Added Personalization link to the admin settings sidebar
  • Added Personalization tab to workspace settings, visible only when the feature is enabled
  • Default role users can now access workspace settings (only the Personalization tab) when memory is enabled, with proper redirects for all other tabs
  • Added frontend Memory API model for all memory endpoints
  • Added shared MemoryForm and MemoryItem components used across both admin and workspace personalization pages
  • Added memory_enabled to system settings supported/public fields and exposed MemoryEnabled in the system keys response
  • Added single-user to multi-user migration for both memories and workspace chats (assigns orphaned records to the admin user)

Visuals (if applicable)

Screenshot 2026-04-10 at 4 56 50 PM Screenshot 2026-04-10 at 4 56 26 PM Screenshot 2026-04-10 at 4 56 36 PM

Additional Information

  • Test CRUD for all areas where we can add/promote to global memories
  • Throughly test RBAC ensuring memories are paired to user accounts in multi-user mode
  • Ensure pending memories are migrated to the new admin account created when enabling multi-user mode and there are no orphaned memories

Developer Validations

  • I ran yarn lint from the root of the repo & committed changes
  • Relevant documentation has been updated (if applicable)
  • I have tested my code functionality
  • Docker build succeeds locally

@shatfield4 shatfield4 self-assigned this Mar 26, 2026
@shatfield4 shatfield4 changed the title feat: memories feat: Memories/Personalization Mar 27, 2026
@shatfield4 shatfield4 mentioned this pull request Mar 27, 2026
10 tasks
@shatfield4 shatfield4 marked this pull request as ready for review March 27, 2026 02:10
Copy link
Copy Markdown
Contributor

@angelplusultra angelplusultra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly nits and refactor suggestions

Comment thread frontend/src/components/Memories/MemoryForm/index.jsx Outdated
Comment thread frontend/src/pages/Admin/Personalization/index.jsx Outdated
Comment thread frontend/src/pages/WorkspaceSettings/index.jsx Outdated
Comment thread frontend/src/main.jsx
Comment thread server/utils/BackgroundWorkers/index.js Outdated
Comment thread server/jobs/extract-memories.js Outdated
Comment thread server/endpoints/memory.js Outdated
@shatfield4
Copy link
Copy Markdown
Collaborator Author

Docs PR: Mintplex-Labs/anythingllm-docs#235

Copy link
Copy Markdown
Contributor

@angelplusultra angelplusultra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some refactor suggestions and nits

Comment thread server/jobs/extract-memories.js
Comment thread server/jobs/extract-memories.js Outdated
Comment thread server/models/memory.js
Comment thread server/endpoints/memory.js
Comment thread frontend/src/components/WorkspaceChat/ChatContainer/ChatSettingsMenu/index.jsx Outdated
Comment thread frontend/src/components/WorkspaceChat/ChatContainer/MemoriesSidebar/index.jsx Outdated
Copy link
Copy Markdown
Contributor

@angelplusultra angelplusultra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about agent chat path missing memory reranking

Comment thread server/utils/agents/defaults.js
Copy link
Copy Markdown
Contributor

@angelplusultra angelplusultra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Just delete that stale comment.

Comment thread server/utils/memories/index.js Outdated
Comment thread frontend/src/components/WorkspaceChat/ChatContainer/ChatSettingsMenu/index.jsx Outdated
Comment thread server/jobs/extract-memories.js
regen migration so it is latest file
@timothycarambat
Copy link
Copy Markdown
Member

Screenshot 2026-04-22 at 7 23 41 PM

If the workspace name is long it overflows the sidebar and is side scrollable, it should truncate to some specific length to prevent this.

@timothycarambat
Copy link
Copy Markdown
Member

Screenshot 2026-04-22 at 7 25 21 PM

This can be wider as to not breakline

Copy link
Copy Markdown
Member

@timothycarambat timothycarambat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder!

This needs a documentation PR to inform users how this works, what it does, how to manage memories - basically a short tutorial on our features so that they can be informed about this new feature to take advantage of!

Notes:

  • When clicking edit on a memory the cursor goes to the start of the input and either should go to the end
  • If i have 5/5 Global memories and I have the global tab focused in sidebar and I click to add another the modal closes like it was successful but nothing happens.
  • the system settings check value for memories needs to be moved from "on"|"off" to "true"|"false" and reading the value should work like other fields. I know that value is a string column so it is storing that as a string but just make a helper in SystemSettings for this like the other boolean/string fields to compensate for it like - https://github.com/Mintplex-Labs/anything-llm/blob/feat/memory/server/models/systemSettings.js#L624-L632 so everything is normalized across all system settings.
  • See line level comments for more

Look above for QA misses in the UI i noticed

Things I did:

  • Refactored how Chat menu was broken up
  • Refactored the sidebar to prevent prop drilling and cleanup DOM and nested component rendering waterfalls

});
},

demoteToWorkspace: async function (memoryId, workspaceId) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSDoc all these so we have type hints in UI

})
.then((res) => res.json())
.catch((e) => {
console.error(e);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont need these error logs since we return error anyway

Comment thread frontend/src/main.jsx
"@/pages/WorkspaceSettings"
);
return { element: <ManagerRoute Component={WorkspaceSettings} /> };
return { element: <PrivateRoute Component={WorkspaceSettings} /> };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we change scope of this route?

Comment on lines +17 to +26
async function memoryEnabled(_req, response, next) {
const enabled =
(await SystemSettings.getValueOrFallback(
{ label: "memory_enabled" },
"off"
)) === "on";
if (!enabled)
return response.status(403).json({ error: "Personalization is disabled." });
next();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this memoryFeatureEnabled and once you migrate the check for this to a method in SystemSettings like I mentioned in parent review comment you can then just call await SystemSettings.memoriesEnabled() and throw if not.

Comment on lines +11 to +15
function ownerMatch(memory, user) {
const memUserId = memory.userId ?? null;
const reqUserId = user?.id ?? null;
return memUserId === reqUserId;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also just be a middleware - it should just be skipped if the system is not in MuM.

Easy to check since the first middleware is validatedRequest which assigns
response.locals.multiUserMode = multiUserMode;

Comment on lines +18 to +35
CREATE TABLE "new_workspace_chats" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"workspaceId" INTEGER NOT NULL,
"prompt" TEXT NOT NULL,
"response" TEXT NOT NULL,
"include" BOOLEAN NOT NULL DEFAULT true,
"user_id" INTEGER,
"thread_id" INTEGER,
"api_session_id" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastUpdatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"feedbackScore" BOOLEAN,
"memory_processed" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "workspace_chats_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_workspace_chats" ("api_session_id", "createdAt", "feedbackScore", "id", "include", "lastUpdatedAt", "prompt", "response", "thread_id", "user_id", "workspaceId") SELECT "api_session_id", "createdAt", "feedbackScore", "id", "include", "lastUpdatedAt", "prompt", "response", "thread_id", "user_id", "workspaceId" FROM "workspace_chats";
DROP TABLE "workspace_chats";
ALTER TABLE "new_workspace_chats" RENAME TO "workspace_chats";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this works but i REALLY do not ever want to trust SQLite to do a full drop-and-replace of the most important table in the entire DB - you can avoid this but not adding a default field to memory_processed

workspaceId Int? @map("workspace_id")
scope String @default("workspace")
content String
sourceThreadId Int? @map("source_thread_id")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked over the code and done see really any place this field even is used in a way that matters? Where is this coming from and/or how is it used?

Comment on lines 70 to 74
const basePrompt = await Provider.systemPrompt({
provider,
workspace,
user,
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not just augment the prompt directly with memories in this method since we already have the user and workspace potentially in there as well?

// Compress & Assemble message to ensure prompt passes token limit with room for response
// and build system messages based on inputs and history.
const systemPrompt = await promptWithMemories({
systemPrompt: await chatPrompt(workspace, user),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - can we augment the chat in this already async method?

const { Memory } = require("../../models/memory");
const { SystemSettings } = require("../../models/systemSettings");

const INJECTED_WORKSPACE_LIMIT = 5;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a description - I did not realize what it did for a while

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: Memories/Personalization

3 participants