diff --git a/apps/web/components/settings/org-context.tsx b/apps/web/components/settings/org-context.tsx
new file mode 100644
index 000000000..dac97f7e7
--- /dev/null
+++ b/apps/web/components/settings/org-context.tsx
@@ -0,0 +1,385 @@
+"use client"
+
+import { useEffect, useMemo, useState } from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { LoaderIcon, Settings, X } from "lucide-react"
+import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
+import { useOrgSettings, useUpdateOrgSettings } from "@/hooks/use-org-settings"
+import { cn } from "@lib/utils"
+import { Dialog, DialogContent, DialogTitle } from "@ui/components/dialog"
+
+type ContextTemplate = {
+ id: string
+ label: string
+ description: string
+ prompt: string
+}
+
+const SURFACE_SHADOW =
+ "0 2.842px 14.211px 0 rgba(0,0,0,0.25), 0.711px 0.711px 0.711px 0 rgba(255,255,255,0.10) inset"
+
+const CONTEXT_TEMPLATES: ContextTemplate[] = [
+ {
+ id: "personal-general",
+ label: "General Personal Assistant",
+ description:
+ "Remember preferences, routines, relationships, plans, and life context.",
+ prompt: `Supermemory personal assistant. The user saves conversations, notes, and daily context.
+
+EXTRACT:
+- Preferences: "prefers morning meetings", "allergic to peanuts"
+- Routines: "works out every Tuesday and Thursday"
+- Relationships: "Sarah is their manager", "lives with roommate Jake"
+- Plans: "planning a trip to Japan in March"
+- Life events: "moved to Austin last month", "started a new job"
+
+SKIP:
+- Generic assistant suggestions the user did not confirm
+- Pleasantries and small talk without factual content
+- Repeated scheduling details already captured`,
+ },
+ {
+ id: "personal-productivity",
+ label: "Productivity Assistant",
+ description:
+ "Focus on tasks, decisions, deadlines, workflows, and blockers.",
+ prompt: `Supermemory productivity tool. The user saves meeting notes, task updates, and project context.
+
+EXTRACT:
+- Action items: "needs to send proposal to client by Friday"
+- Decisions: "team decided to use Figma for design handoff"
+- Deadlines: "Q3 review due September 15th"
+- Workflows: "deploys happen every Wednesday via CI pipeline"
+- Blockers: "waiting on legal approval before launch"
+
+SKIP:
+- Meeting filler ("let's circle back", "good point")
+- Status updates that repeat previously captured information
+- Agenda items with no outcome or decision`,
+ },
+]
+
+function SectionTitle({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+function SettingsCard({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+function PillButton({
+ children,
+ onClick,
+ disabled,
+ variant = "default",
+}: {
+ children: React.ReactNode
+ onClick: () => void
+ disabled?: boolean
+ variant?: "default" | "danger" | "primary"
+}) {
+ return (
+
+ )
+}
+
+export function OrgContext() {
+ const { data: settings, isLoading, isError } = useOrgSettings()
+ const updateSettings = useUpdateOrgSettings()
+ const [confirmDialog, setConfirmDialog] = useState<
+ "enable" | "disable" | null
+ >(null)
+ const [isManaging, setIsManaging] = useState(false)
+ const [prompt, setPrompt] = useState("")
+
+ const enabled = settings?.shouldLLMFilter ?? false
+ const savedPrompt = settings?.filterPrompt ?? ""
+ const dirty = prompt.trim() !== savedPrompt.trim()
+ const settingsReady = !isLoading && !isError
+
+ useEffect(() => {
+ setPrompt(settings?.filterPrompt ?? "")
+ }, [settings?.filterPrompt])
+
+ const selectedTemplateId = useMemo(() => {
+ const normalized = prompt.trim()
+ return CONTEXT_TEMPLATES.find(
+ (template) => template.prompt.trim() === normalized,
+ )?.id
+ }, [prompt])
+
+ const handleConfirmToggle = () => {
+ const newEnabled = confirmDialog === "enable"
+ updateSettings.mutate(
+ newEnabled
+ ? { shouldLLMFilter: true }
+ : { shouldLLMFilter: false, filterPrompt: null },
+ {
+ onSuccess: () => {
+ setConfirmDialog(null)
+ if (!newEnabled) {
+ setIsManaging(false)
+ setPrompt("")
+ }
+ },
+ },
+ )
+ }
+
+ const handleSave = () => {
+ updateSettings.mutate(
+ {
+ shouldLLMFilter: true,
+ filterPrompt: prompt.trim() ? prompt.trim() : null,
+ },
+ {
+ onSuccess: () => {
+ setIsManaging(false)
+ },
+ },
+ )
+ }
+
+ const handleCancel = () => {
+ setPrompt(savedPrompt)
+ setIsManaging(false)
+ }
+
+ return (
+
+
+
Organization Context
+
+ Guide how Nova processes and remembers your content.
+
+
+
+
+
+
+
+
+ {enabled ? "Context is enabled" : "Context is disabled"}
+
+
+ {enabled
+ ? "New content will use your guidance when Nova creates memories."
+ : "Turn this on to tell Nova what matters most when it learns."}
+
+
+
+
+
setConfirmDialog(enabled ? "disable" : "enable")}
+ disabled={!settingsReady || updateSettings.isPending}
+ variant={enabled ? "danger" : "primary"}
+ >
+ {enabled ? "DISABLE" : "ENABLE"}
+
+ {enabled && (
+
setIsManaging(true)}
+ disabled={updateSettings.isPending}
+ >
+
+ MANAGE
+
+ )}
+
+
+
+ {enabled && !isManaging && savedPrompt && (
+
+ )}
+
+ {enabled && !isManaging && !savedPrompt && (
+
+ No organization context configured.{" "}
+
+
+ )}
+
+ {enabled && isManaging && (
+
+
+
+
+
+ {CONTEXT_TEMPLATES.map((template) => {
+ const isSelected = selectedTemplateId === template.id
+ return (
+
+ )
+ })}
+
+
+
+
+
CANCEL
+
+ {updateSettings.isPending && (
+
+ )}
+ SAVE
+
+
+
+ )}
+
+
+
+
+
+ )
+}
diff --git a/apps/web/components/space-profile-modal.tsx b/apps/web/components/space-profile-modal.tsx
new file mode 100644
index 000000000..b9fe14b0e
--- /dev/null
+++ b/apps/web/components/space-profile-modal.tsx
@@ -0,0 +1,39 @@
+"use client"
+
+import { dmSansClassName } from "@/lib/fonts"
+import { SpaceProfileContent } from "@/components/space-profile-panel"
+import { cn } from "@lib/utils"
+import { Dialog, DialogContent, DialogTitle } from "@ui/components/dialog"
+
+type SpaceProfileModalProps = {
+ containerTag: string
+ open: boolean
+ onOpenChange: (open: boolean) => void
+}
+
+export function SpaceProfileModal({
+ containerTag,
+ open,
+ onOpenChange,
+}: SpaceProfileModalProps) {
+ return (
+
+ )
+}
diff --git a/apps/web/components/space-profile-panel.tsx b/apps/web/components/space-profile-panel.tsx
new file mode 100644
index 000000000..11d83a042
--- /dev/null
+++ b/apps/web/components/space-profile-panel.tsx
@@ -0,0 +1,168 @@
+"use client"
+
+import { Brain, X } from "lucide-react"
+import { motion } from "motion/react"
+import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
+import { useSpaceProfile } from "@/hooks/use-space-profile"
+import { cn } from "@lib/utils"
+
+type SpaceProfilePanelProps = {
+ containerTag: string
+ isOpen: boolean
+ onClose: () => void
+}
+
+type SpaceProfileContentProps = {
+ containerTag: string
+ onClose?: () => void
+}
+
+function CountBadge({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+function LoadingState() {
+ return (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+ ))}
+
+ )
+}
+
+function EmptyState() {
+ return (
+
+
+
+
+
No profile yet
+
+ Add more memories and Nova will learn about this space.
+
+
+ )
+}
+
+function ProfileSection({ label, items }: { label: string; items: string[] }) {
+ if (items.length === 0) return null
+
+ return (
+
+
+
+ {label}
+
+ {items.length}
+
+
+ {items.map((item, index) => (
+
+ ))}
+
+
+ )
+}
+
+export function SpaceProfileContent({
+ containerTag,
+ onClose,
+}: SpaceProfileContentProps) {
+ const { data, isLoading, error } = useSpaceProfile(containerTag)
+ const keyFacts = data?.static ?? []
+ const recentContext = data?.dynamic ?? []
+ const totalCount = keyFacts.length + recentContext.length
+
+ return (
+
+
+
+
+ Space Profile
+
+
+ What Nova knows in this space
+
+
+ {onClose ? (
+
+ ) : null}
+
+
+
+ {isLoading ? (
+
+ ) : error ? (
+
+ Failed to load space profile.
+
+ ) : totalCount === 0 ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export function SpaceProfilePanel({
+ containerTag,
+ isOpen,
+ onClose,
+}: SpaceProfilePanelProps) {
+ if (!isOpen) return null
+
+ return (
+
+
+
+ )
+}
diff --git a/apps/web/hooks/use-org-settings.ts b/apps/web/hooks/use-org-settings.ts
new file mode 100644
index 000000000..dc415ff17
--- /dev/null
+++ b/apps/web/hooks/use-org-settings.ts
@@ -0,0 +1,80 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
+import { toast } from "sonner"
+import { $fetch } from "@lib/api"
+import { useAuth } from "@lib/auth-context"
+
+const API_BASE = `${process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"}/v3`
+
+export type OrgSettings = {
+ shouldLLMFilter: boolean
+ filterPrompt: string | null
+ includeItems?: string[] | null
+ excludeItems?: string[] | null
+}
+
+type OrgSettingsResponse = {
+ settings?: Partial
+} & Partial
+
+export function useOrgSettings() {
+ const { org } = useAuth()
+ const orgId = org?.id ?? ""
+
+ return useQuery({
+ queryKey: ["settings", "org", orgId],
+ queryFn: async (): Promise => {
+ const response = await $fetch("@get/settings", {
+ disableValidation: true,
+ })
+ if (response.error) {
+ throw new Error(response.error.message || "Failed to load settings")
+ }
+ const data = response.data as OrgSettingsResponse | null
+ const settings = data?.settings ?? data ?? {}
+ return {
+ shouldLLMFilter: settings.shouldLLMFilter ?? false,
+ filterPrompt: settings.filterPrompt ?? null,
+ includeItems: settings.includeItems ?? null,
+ excludeItems: settings.excludeItems ?? null,
+ }
+ },
+ enabled: !!orgId,
+ staleTime: 60 * 1000,
+ })
+}
+
+export function useUpdateOrgSettings() {
+ const { org } = useAuth()
+ const queryClient = useQueryClient()
+ const orgId = org?.id ?? ""
+
+ return useMutation({
+ mutationFn: async (settings: Partial) => {
+ const res = await fetch(`${API_BASE}/settings`, {
+ method: "PATCH",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ "X-App-Source": "nova",
+ },
+ body: JSON.stringify(settings),
+ })
+ if (!res.ok) {
+ const body = (await res.json().catch(() => ({}))) as {
+ message?: string
+ }
+ throw new Error(body?.message || "Failed to save settings")
+ }
+ return res.json()
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["settings", "org", orgId] })
+ toast.success("Settings saved")
+ },
+ onError: (error) => {
+ toast.error(
+ error instanceof Error ? error.message : "Failed to save settings",
+ )
+ },
+ })
+}
diff --git a/apps/web/hooks/use-space-profile.ts b/apps/web/hooks/use-space-profile.ts
new file mode 100644
index 000000000..9ee2114d9
--- /dev/null
+++ b/apps/web/hooks/use-space-profile.ts
@@ -0,0 +1,37 @@
+import { useQuery } from "@tanstack/react-query"
+import { $fetch } from "@lib/api"
+import { useAuth } from "@lib/auth-context"
+
+export type SpaceProfile = {
+ static: string[]
+ dynamic: string[]
+}
+
+export function useSpaceProfile(containerTag: string) {
+ const { org } = useAuth()
+ const orgId = org?.id ?? ""
+
+ return useQuery({
+ queryKey: ["space-profile", orgId, containerTag],
+ queryFn: async (): Promise => {
+ const response = await $fetch(
+ "@get/container-tags/:containerTag/profile",
+ {
+ params: { containerTag },
+ },
+ )
+ if (response.error) {
+ throw new Error(
+ response.error.message || "Failed to load space profile",
+ )
+ }
+ const profile = response.data.profile
+ return {
+ static: profile.static ?? [],
+ dynamic: profile.dynamic ?? [],
+ }
+ },
+ enabled: !!orgId && !!containerTag,
+ staleTime: 60 * 1000,
+ })
+}
diff --git a/packages/lib/api.ts b/packages/lib/api.ts
index 76d62fda9..4ce787f23 100644
--- a/packages/lib/api.ts
+++ b/packages/lib/api.ts
@@ -164,7 +164,7 @@ export const apiSchema = createSchema({
// Settings operations
"@get/settings": {
- output: z.object({ settings: z.object({}).passthrough() }),
+ output: z.object({}).passthrough(),
},
"@patch/settings": {
input: SettingsRequestSchema,
@@ -254,6 +254,17 @@ export const apiSchema = createSchema({
"@get/container-tags/list": {
output: ListContainerTagsResponseSchema,
},
+ "@get/container-tags/:containerTag/profile": {
+ output: z.object({
+ profile: z.object({
+ static: z.array(z.string()).optional(),
+ dynamic: z.array(z.string()).optional(),
+ }),
+ }),
+ params: z.object({
+ containerTag: z.string(),
+ }),
+ },
"@patch/container-tags/:containerTag": {
input: UpdateContainerTagSettingsRequestSchema,
output: ContainerTagSettingsUpdateSchema,