The AI workspace uses structured context instead of one giant hardcoded prompt. Product knowledge, feature lists, legal context, available pages, user context, project sources, and conversation history are assembled before the model runs.
Context Sources
Shared product context lives in src/features/ai/chat/constants/ai-shared-context.constants.ts.
The important exports are:
SYSTEM_LEVEL_INSTRUCTIONSfor assistant behavior.PRODUCT_DESCRIPTION_CONTEXTfor what Spacerr is.PRODUCT_FEATURES_CONTEXTfor the product feature list.TERMS_OF_SERVICE_CONTEXTfor policy answers.PRIVACY_POLICY_CONTEXTfor privacy answers.AVAILABLE_APP_PAGESfor pages the assistant can link to.AVAILABLE_APP_PAGES_CONTEXTfor model ready page context.
Use this file when you want to change how the assistant explains the product, what pages it can recommend, what policies it knows, or how it should speak.
export const SYSTEM_LEVEL_INSTRUCTIONS: string[] = [
`You are the AI assistant for ${PRODUCT_NAME}.`,
"Always use absolute URLs.",
]
export const PRODUCT_DESCRIPTION_CONTEXT: string[] = [
`${PRODUCT_NAME} is a production ready AI SaaS starter kit.`,
]Prompt Assembly
The prompt is assembled in src/features/ai/chat/utils/chat-system-prompt.server.ts.
export function buildChatSystemPrompt({ pageUrl, sessionUser }: BuildChatSystemPromptParams): string {
return buildChatSystemPromptContext([
{ title: "System Instructions", lines: SYSTEM_LEVEL_INSTRUCTIONS },
{ title: "Product Description Context", lines: PRODUCT_DESCRIPTION_CONTEXT },
{ title: "Product Features Context", lines: PRODUCT_FEATURES_CONTEXT },
{ title: "Privacy Context", lines: PRIVACY_POLICY_CONTEXT },
{ title: "Available Pages", lines: AVAILABLE_APP_PAGES_CONTEXT },
{ title: "Current User Context", lines: getUserContextLines(sessionUser) },
{ title: "Current Page URL", lines: pageUrl ? [pageUrl] : [] },
])
}Each section becomes a titled block. This keeps context readable and easier to remove, replace, or reorder.
The same pattern is simple to extend. buildChatSystemPromptContext accepts a typed array of sections, so you can add optional extra context without changing the formatter.
import type { BuildChatSystemPromptContextSection } from "@/features/ai/chat/utils/build-chat-system-prompt-context.server"
type BuildChatSystemPromptParams = {
extraSections?: readonly BuildChatSystemPromptContextSection[]
pageUrl: string | null
sessionUser: Awaited<ReturnType<typeof getSessionUser>>
}
export function buildChatSystemPrompt({
extraSections = [],
pageUrl,
sessionUser,
}: BuildChatSystemPromptParams): string {
return buildChatSystemPromptContext([
{ title: "System Instructions", lines: SYSTEM_LEVEL_INSTRUCTIONS },
{ title: "Product Description Context", lines: PRODUCT_DESCRIPTION_CONTEXT },
{ title: "Product Features Context", lines: PRODUCT_FEATURES_CONTEXT },
{ title: "Privacy Context", lines: PRIVACY_POLICY_CONTEXT },
{ title: "Available Pages", lines: AVAILABLE_APP_PAGES_CONTEXT },
{ title: "Current User Context", lines: getUserContextLines(sessionUser) },
{ title: "Current Page URL", lines: pageUrl ? [pageUrl] : [] },
...extraSections,
])
}For async context, fetch the data first, turn it into the same section shape, and pass it through extraSections.
const accountContextLines = await getAccountContextLines(sessionUser?.id)
const prompt = buildChatSystemPrompt({
extraSections: [{ title: "Account Context", lines: accountContextLines }],
pageUrl,
sessionUser,
})That keeps the core builder stable while letting you add billing context, workspace context, CRM context, feature flags, permissions, or any other sync or async product data.
User Context
Authenticated user context is added by getUserContextLines inside chat-system-prompt.server.ts.
The app removes sensitive fields such as id and image before passing user context to the model. This lets the assistant answer account related questions without immediately calling tools for information it already has.
Conversation Context
Conversation context is prepared in src/features/ai/chat/utils/prepare-chat-model-context.server.ts.
That function combines:
- The current message list.
- The existing conversation summary.
- Recent messages.
- The chat file manifest for files attached in this chat.
- Remembered source context for files the assistant has fetched into this chat.
- Reserved prompt text so the app does not overfill the model context.
The context budget logic lives in src/features/ai/chat/utils/chat-context-budget.utils.ts. That file is used by the active context pipeline. It keeps recent messages, adds the saved summary when available, and drops older content only when the prompt needs to fit the configured budget.
The main knobs live in src/features/ai/chat/constants/chat-model.constants.ts.
export const CONTEXT_BUDGET_MAX_CHARS = 24_000
export const CONTEXT_BUDGET_RECENT_WINDOW_SIZE = 16
export const CONTEXT_SUMMARY_BATCH_SIZE = 6
export const CONTEXT_SUMMARY_MAX_CHARS = 10_000
export const CONTEXT_SUMMARY_TARGET_CHARS = 6_000Change these constants when you want shorter context, more recent message history, or a different summary refresh cadence.
File And Source Context
File and source context is split into lightweight memory and explicit reads.
When the user uploads a file in the latest message, toModelReadyMessages can fetch the Blob file and pass readable content to the model for that turn. On later turns, older file parts are converted into lightweight text references so the app does not keep re sending the same Blob bytes.
The source memory layer is built from:
message_sourcerecords for files that belong to chat messages.project_sourcerecords for files uploaded directly into a project.chat_context_sourcerecords for sources remembered inside a specific chat.- Hidden model context messages for the current chat file manifest and remembered source context.
The assistant can search available user files with searchUserSources and read a relevant file with readUserSource. Search checks Library files, project sources, and files from project chats. Read remembers the source in the current chat so follow up messages can use the saved context.
The source discovery code lives mainly in:
src/features/ai/chat/repositories/user-source.repository.ts
src/features/ai/chat/tools/chat-user-source-tools.server.ts
src/features/ai/chat/utils/user-source-formatting.server.ts
src/features/ai/chat/utils/source-summary.server.ts
src/features/ai/chat/utils/chat-model-ready-messages.server.tsThe important constants are:
export const CHAT_CONTEXT_SOURCE_LIMIT = 6
export const CHAT_CONTEXT_SOURCE_MAX_TEXT_CHARS = 18_000
export const CHAT_CONTEXT_SOURCE_MAX_ATTACHMENT_BYTES = 12 * 1024 * 1024
export const CHAT_FILE_MANIFEST_LIMIT = 80
export const USER_SOURCE_SEARCH_DEFAULT_LIMIT = 6
export const USER_SOURCE_SEARCH_MAX_LIMIT = 10
export const USER_SOURCE_FETCH_MAX_CONTENT_CHARS = 12_000Use source context when you want the assistant to answer questions about uploaded Library files, project files, screenshots, PDFs, spreadsheets, JSON, CSV, Markdown, or text files.
Good Customization Order
When customizing assistant behavior, change files in this order:
- Update product facts in
ai-shared-context.constants.ts. - Update page links in
AVAILABLE_APP_PAGES. - Update model options in
chat-model.constants.ts. - Update source search and remembered source limits only if your file workflow needs it.
- Update
chat-system-prompt.server.tsonly when the section order or prompt structure needs to change.