AI memory and conversations
How MaShop keeps context across sessions: saved chat threads, a short active window, and persistent AI memory the model reads on every prompt.
This is an explanation: it describes how MaShop keeps context across a project, what is stored, and where the model reads it.
MaShop keeps context in three separate places. Your chat threads are saved so you can reopen them. A short window of recent messages is the model's working memory inside one thread. A persistent store of brand rules and preferences is read into every prompt, in every thread, until you change it. This page explains each one, where it lives in the code, and the limits that bite.
Three layers of context
| Layer | Table | Scope | What it holds |
|---|---|---|---|
| Conversations | ai_conversations | Per user, per project | Saved chat threads shown in the Conversations panel |
| Active context | ai_sessions | Per user, per project | The last messages of the current thread, fed back to the model |
| AI memory | ai_memory | Per user, per project | Durable brand rules and preferences injected into every prompt |
All three are keyed by your user id and the project id, so a memory or thread from one project never leaks into another, and one collaborator's chats are not visible to another. Row Level Security on ai_memory and ai_sessions limits each row to auth.uid() = user_id, and the ai_conversations list endpoint adds an explicit project-access check so a user without a role on the project gets a 403 Forbidden rather than an empty list.
Conversations: your saved chat threads
Every chat is a thread you can reopen. In the workspace, the History button opens a panel titled Conversations, grouped by period, with a New conversation button at the top. Each row is one ai_conversations record with a title, a model, and the message list. The panel loads through GET /api/conversations?projectId=..., which returns the 50 most recent threads ordered by last update.
Selecting a thread loads its messages through GET /api/conversations/{id} and restores its session id, so you continue where you left off. Renaming, archiving, and deleting go through PATCH and DELETE on the same route. Creating a thread requires the editor role; a viewer can read threads but cannot start one or send a message.
Threads are per collaborator. Each person on a shared project has their own chat history, even though the project, code, and database are shared. Every conversation query is scoped with user_id = auth.uid(), so collaborator A never reads collaborator B's threads.
Active context: the working window inside one thread
Inside a single thread, the model does not see the whole history. Before each reply, MaShop loads the most recent session and passes its messages back to the model. That window is capped: MAX_HISTORY_MESSAGES = 20 in apps/platform/src/lib/ai/memory.ts, which is the last 20 messages, roughly 10 turns. Older turns fall out of the active window and the model stops seeing them.
This is why the chat surfaces a nudge once a single conversation gets very long. The threshold is SESSION_MSG_NUDGE_THRESHOLD = 120 turns in the builder. The nudge is non-blocking and dismissible: there is no hard message cap, but past the active window the older turns silently drop out, so starting a new conversation gives the model a clean window and better answers.
The active window lives in ai_sessions as a messages JSON array. After each reply, MaShop appends your message and the assistant's reply and trims to the most recent 20 before saving. Stale sessions are pruned: cleanupExpiredSessions() deletes sessions whose updated_at is older than 24 hours by default.
AI memory: durable rules read into every prompt
AI memory is the layer that survives across threads and across sessions. It is a set of short key and value entries the model reads before every reply, in every conversation on the project, until you change them. A typical entry is a brand rule such as brand_color: deep navy, never bright blue or a hard constraint such as a thing the model must never do.
Each entry has a category. The database constrains the column to five values:
brandfor brand identity: tone, voice, sector, audience.preferencesfor design and layout choices: palette, typography, section style.forbiddenfor things the model must never do.contextfor general facts about the project.historyfor prior decisions worth keeping.
Where the model reads it
On every prompt, MaShop loads your active memories and folds them into the context block it sends to the model. formatMemoriesForPrompt() groups the entries by category and wraps them in a <merchant_memory> block, which buildContext() places alongside your store name, type, currency, and item count. The model treats that block as standing instructions for the whole reply, so a saved rule shapes generation, copy, and section choices without you restating it each time.
<merchant_memory>
[brand]
tone: warm, plainspoken, never corporate jargon
[forbidden]
no_stock_photos: never use generic stock photography
</merchant_memory>
How entries get written
Memory is written three ways, all wired:
- You ask the model to remember. When you say something like "always keep the header dark" or "never use rounded buttons," the model calls the
save_preferencetool, which upserts one entry. The save costs 0 credits. - The model extracts it after a conversation. When a thread has at least 6 messages and contains design or preference signals, MaShop analyzes the conversation in the background and saves durable preferences it finds. It keeps only entries it scores at confidence 0.6 or higher, caps the run at 8 new entries, runs at most once every 4 hours per project, and never overwrites an entry you saved explicitly.
- MaShop consolidates over time. A background pass merges duplicate entries, resolves contradictions in favor of the newer value, and prunes vague ones. It runs only when three gates all pass: at least 24 hours since the last consolidation, at least 3 sessions since then, and at least 5 stored memories. It keeps at most 50 entries per project and never deletes a
forbiddenrule except on an explicit contradiction.
The background extraction and consolidation are fire and forget: they run after your reply is sent, so they never slow the chat down. If they fail, the chat is unaffected and the model works from whatever memory already exists.
Scope, updates, and expiry
Memory is unique on user id, project id, and key, so writing the same key updates the existing entry instead of duplicating it. The newest value wins. An entry can carry an optional expiry; entries past their expiry are filtered out on read and deleted by a cleanup pass, while entries with no expiry persist until consolidation prunes them or you overwrite them.
What memory does not do
Memory is per project. A rule you save while building one store does not follow you into another project. There is no shipped settings panel that lists or edits memory entries directly: you manage memory through the chat, by telling the model what to remember or what to change. The 20-message active window is per thread and is not a memory of the whole project; durable facts belong in AI memory, not in a long single thread.
Privacy
Your prompts and generated code are not sent to OpenAI or Anthropic. Memory, conversations, and the active window are stored in MaShop's own database under Row Level Security scoped to your account, and the conversation list endpoint rejects any request that lacks a role on the project.
Related
- How building works covers the tools the model calls, including
save_preference. - Collaborators explains the owner, editor, and viewer roles that gate who can chat.
- Projects explains the per-project scope that memory and conversations inherit.
