From a39a802bbc93e85b820078cd3c4dbaf53af25dbe Mon Sep 17 00:00:00 2001 From: Himanshu <104683257+Himanshu-Sangshetti@users.noreply.github.com> Date: Fri, 18 Sep 2026 23:19:15 +0530 Subject: [PATCH] docs(cookbooks): Company Brain with Mem0 Platform + Supabase (#7309) --- docs/cookbooks/integrations/supabase.mdx | 388 +++++++++++++++++++++++ docs/docs.json | 3 +- docs/llms.txt | 1 + 3 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 docs/cookbooks/integrations/supabase.mdx diff --git a/docs/cookbooks/integrations/supabase.mdx b/docs/cookbooks/integrations/supabase.mdx new file mode 100644 index 000000000..cf9b8c038 --- /dev/null +++ b/docs/cookbooks/integrations/supabase.mdx @@ -0,0 +1,388 @@ +--- +title: Build a Company Brain with Mem0 Platform and Supabase +description: "A step-by-step guide to a shared organizational memory: Mem0 Platform is the managed brain, Supabase is your system of record, and every agent taps it through the Mem0 MCP server." +--- + + +**Uses:** Mem0 **Platform** (`MemoryClient`) · **System of record:** Supabase (Postgres + Auth) · **Access layer:** the hosted Mem0 MCP server. **You'll build:** a company brain your whole org (and every agent) writes to and queries, ending with a new-hire onboarding demo. + + +Companies lose knowledge constantly: why you picked Postgres over Mongo, who owns billing, the deploy rule only one engineer remembers. A **company brain** captures this and answers questions about it, for every employee and every agent, and keeps it after people leave. + +We'll build one on **Mem0 Platform** (the managed memory layer, so there's no vector DB to run) with **Supabase as the system of record** (where your employees, teams, and source documents actually live) and the **Mem0 MCP server** as the wire that lets Claude Code, Cursor, or a Slack bot all reach the same brain. + + +**How Platform and Supabase divide the work.** Mem0 Platform manages storage and extraction server-side, you do **not** point it at your own database. Supabase is your app's source of truth and identity provider; we *ingest* knowledge from Supabase into the brain and use Supabase Auth to decide who's asking. (If you want to self-host the vector store instead, that's the OSS path, see the [Supabase vector store reference](/components/vectordbs/dbs/supabase).) + + +## Architecture + +```mermaid +flowchart LR + subgraph SB["Supabase: system of record"] + K[(knowledge / employees / teams)] + AU[Auth · who is asking] + end + subgraph M0["Mem0 Platform: the brain"] + B[(managed memory)] + end + K -->|ingest| B + AU -->|maps to scope| B + CC[Claude Code] --> MCP[Mem0 MCP server] + CU[Cursor] --> MCP + SL[Slack bot] --> MCP + MCP --> B +``` + +Memory splits by entity. An individual is a **`user_id`** (their Supabase Auth id). Shared knowledge lives on an **`agent_id`**: the company-wide brain is `org:acme`, and each team is its own agent, e.g. `team:payments`. A person's own facts route to their `user_id`; company and team facts route to the agent. This split is what lets one search return "my" context alongside the shared org knowledge. + +## Prerequisites + +- **Python 3.9+** +- A **Mem0 Platform API key**, [app.mem0.ai/dashboard/api-keys](https://app.mem0.ai/dashboard/api-keys?utm_source=oss&utm_medium=cookbook-company-brain). (Platform runs extraction and embeddings for you, so there's no OpenAI key to manage.) +- A **Supabase** project, [supabase.com](https://supabase.com) + +About 20 minutes. + +--- + +## Step 1: Get your Mem0 Platform API key + +Sign in at [app.mem0.ai](https://app.mem0.ai) and copy a key from **Dashboard → API Keys**. The key is scoped to your org and project; Mem0 resolves both server-side, so you never pass IDs by hand. + +## Step 2: Create the Supabase system of record + +In the Supabase **SQL editor**, create the tables your company already thinks in: people, teams, and a `knowledge` table the brain will ingest from. Identity reuses Supabase Auth's built-in `auth.users`. + +```sql +-- Employees extend Supabase Auth's users; identity is auth.users.id (uuid) +create table public.employees ( + id uuid primary key references auth.users (id) on delete cascade, + name text not null, + team text not null +); + +-- The company knowledge the brain ingests. `scope` decides who can recall it. +create table public.knowledge ( + id bigint generated always as identity primary key, + scope text not null, -- the shared agent this belongs to: 'org:acme' | 'team:payments' + content text not null, + author uuid references auth.users (id), -- who recorded it (their user_id); null for org seed data + created_at timestamptz default now(), + mem0_synced_at timestamptz -- null until ingested into the brain +); +create index on public.knowledge (mem0_synced_at, created_at); + +-- Seed a little company knowledge to ingest. +insert into public.knowledge (scope, content) values + ('org:acme', 'We chose Postgres over MongoDB for the core product for strong transactional guarantees and relational joins.'), + ('org:acme', 'All production deploys go out Tuesday and Thursday; never on Fridays.'), + ('org:acme', 'Customer data must stay in the EU region for GDPR compliance.'), + ('org:acme', 'Billing is owned by the Payments team, and Alice is the Payments tech lead.'), + ('team:payments', 'Stripe is our processor; webhooks are verified with PAYMENTS_WEBHOOK_SECRET.'); +``` + +Grab your project URL and **service-role** key from **Settings → API** (the ingestion job runs server-side and needs to read every scope). + +## Step 3: Project setup + +```bash +mkdir company-brain && cd company-brain +pip install "mem0ai>=2.0.17" supabase requests # 2.0.17+ for agent_custom_instructions +``` + +```bash +export MEM0_API_KEY="m0-..." +export SUPABASE_URL="https://.supabase.co" +export SUPABASE_SERVICE_KEY="" +``` + +## Step 4: Configure the brain + +Create **`brain.py`**. This constructs the Platform client and teaches it what to remember. The key is the **two** instruction sets: `custom_instructions` governs a person's own (`user_id`) memories, and `agent_custom_instructions` governs shared (`agent_id`) memories, phrased in the third person so company facts read "The company…", not "The user's organization…". `custom_categories` files each memory under a useful label. + +```python +# brain.py +import os +from mem0 import MemoryClient + +client = MemoryClient(api_key=os.environ["MEM0_API_KEY"]) + +# Steer extraction (project-wide). Runs server-side; no LLM key needed here. +client.project.update( + # Governs a person's OWN memories (user_id). + custom_instructions=( + "Extract the individual's own durable preferences, context, and how they work. " + "Ignore greetings and one-off chatter." + ), + # Governs SHARED memories (agent_id); write them in the third person. + agent_custom_instructions=( + "Extract durable company/team knowledge in the third person " + "(\"The company...\", \"The team...\"): decisions and their rationale, ownership " + "(who owns what), processes, policies, tooling choices, and gotchas. " + "Ignore greetings, scheduling, and one-off chatter." + ), + custom_categories=[ + {"decision": "Architectural or product decisions and why they were made"}, + {"ownership": "Who owns a system, service, or process"}, + {"policy": "Compliance, security, and process rules"}, + {"tooling": "Tools, services, and how they're configured"}, + ], +) + +# Scopes. A person is a user_id; shared brains are agent_ids. +COMPANY = "org:acme" # agent_id: company-wide shared brain +def team(name): return f"team:{name}" # agent_id: a team's shared brain +def person(uid): return uid # user_id: an individual (Supabase auth id) +``` + +Run it once to apply the project settings: + +```bash +python -c "import brain; print('brain configured')" +``` + + +## Step 5: Ingest company knowledge from Supabase + +This is where Supabase and the brain connect. Create **`ingest.py`**: read un-synced rows from `knowledge`, add each to the Platform brain under its scope, then mark it synced. Platform `add()` is **asynchronous**, it returns an `event_id` you can poll, so we include a small `wait_for` helper. + +```python +# ingest.py +import os, time, requests +from supabase import create_client +from brain import client, person + +sb = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_KEY"]) +MEM0_HEADERS = {"Authorization": f"Token {os.environ['MEM0_API_KEY']}"} + +def wait_for(event_id, timeout=30): + """Platform extraction is async; poll the event until it settles.""" + for _ in range(timeout): + r = requests.get(f"https://api.mem0.ai/v1/event/{event_id}/", headers=MEM0_HEADERS).json() + if r.get("status") in ("SUCCEEDED", "FAILED"): + return r["status"] + time.sleep(1) + return "TIMEOUT" + +# 1. Read knowledge that hasn't been ingested yet +rows = sb.table("knowledge").select("*").is_("mem0_synced_at", "null").execute().data + +for row in rows: + # 2. Add it. agent_id = the shared scope (org/team); user_id = who recorded it. + # Mem0 routes shared facts to the agent and personal facts to the individual, + # so pass both when there's an author. + add_kwargs = { + "agent_id": row["scope"], + "metadata": {"source": "supabase", "knowledge_id": row["id"]}, + } + if row["author"]: + add_kwargs["user_id"] = person(row["author"]) + res = client.add([{"role": "user", "content": row["content"]}], **add_kwargs) + # 3. Platform returns an event_id; wait for extraction to finish + event_id = res.get("event_id") if isinstance(res, dict) else None + if event_id: + wait_for(event_id) + # 4. Mark the row synced so we never double-ingest + sb.table("knowledge").update({"mem0_synced_at": "now()"}).eq("id", row["id"]).execute() + +print(f"Ingested {len(rows)} knowledge items into the company brain.") +``` + +```bash +python ingest.py +``` + +```text +Ingested 5 knowledge items into the company brain. +``` + +Re-running is safe, `mem0_synced_at` gates it, so a nightly cron can keep the brain in step with Supabase. + +## Step 6: Ask the brain + +Create **`ask.py`**. It searches everything relevant to the asker: their own (`user_id`) memories **plus** the shared company and team (`agent_id`) memories. This has to be an **`OR`**, each memory row belongs to exactly one entity, so a flat filter or an `AND` of a `user_id` and an `agent_id` matches nothing. + +```python +# ask.py +import sys +from brain import client, COMPANY, team, person + +def ask(question: str, uid: str | None = None, user_team: str | None = None) -> str: + scopes = [{"agent_id": COMPANY}] # company-wide brain + if user_team: + scopes.append({"agent_id": team(user_team)}) # the asker's team + if uid: + scopes.append({"user_id": person(uid)}) # the asker's own memories + hits = client.search( + query=question, + filters={"OR": scopes}, # OR, never AND (one FK per memory row) + top_k=5, + rerank=True, + ) + return "\n".join(f"- {h['memory']}" for h in hits.get("results", hits)) + +if __name__ == "__main__": + print(ask(" ".join(sys.argv[1:]) or "When can we deploy?")) +``` + +```bash +python ask.py "Why did we pick Postgres, and can I deploy on Friday?" +``` + +```text +- The company chose Postgres over MongoDB for strong transactional guarantees and relational joins +- The company's production deploys go out Tuesday and Thursday, never on Fridays +``` + +Search returns every relevant memory, so a question resolves across separate facts, here it pulls both the owning team and the person: + +```bash +python ask.py "Who should I talk to about billing?" +``` + +```text +- Billing is owned by the Payments team +- Alice is the Payments tech lead +``` + +## Step 7: Sharper retrieval + +Platform search is hybrid (semantic + keyword) and filterable. Combine a keyword pass with a category filter to answer precise questions: + +```python +client.search( + query="webhook signing secret", + filters={"agent_id": "team:payments", "categories": {"in": ["tooling"]}}, + keyword_search=True, # hybrid keyword + semantic + rerank=True, + threshold=0.3, +) +``` + +Filters use keyword operators (`in`, `gte`, `contains`, …) and AND/OR/NOT, so you can scope by date, category, or metadata, for example the company's policies added this quarter: + +```python +client.search( + query="compliance rules", + filters={"AND": [ + {"agent_id": "org:acme"}, + {"categories": {"in": ["policy"]}}, + {"created_at": {"gte": "2026-01-01"}}, + ]}, +) +``` + +## Step 8: Expose the brain to every agent (MCP) + +A brain only your script can reach isn't a company brain. Mem0's **hosted MCP server** lets any agent (Claude Code, Cursor, a Slack bot) query and contribute to the *same* brain. The endpoint is `https://mcp.mem0.ai/mcp`, and the supported way to connect is the `mcp-add` helper, which registers the server and runs Mem0's OAuth login so no key ever lands in a config file. + + + +```bash +npx mcp-add --url "https://mcp.mem0.ai/mcp" --clients "claude code,cursor" +``` +Complete the browser login on first connect. Now the agent has the brain's memory tools (`add_memory`, `search_memories`, and more) available in-editor. + + +```json +{ + "mcpServers": { + "mem0": { "url": "https://mcp.mem0.ai/mcp" } + } +} +``` +Auth happens via Mem0's OAuth flow on first use, don't paste a static token into the file (the hosted gateway may reject a raw `Token` header). + + +```python +# A Slack bot is just another MCP client. Point its MCP layer at the same URL, +# authenticate via Mem0's OAuth flow, and pass the company scope on each call. +await mcp.call_tool("search_memories", { + "query": user_message, + "agent_id": "org:acme", +}) +``` + + + +With this, an engineer asks the brain from their editor and a teammate asks it from Slack, one shared memory behind both. + +## Step 9: Onboard a new hire (the payoff) + +This is what a company brain is *for*. Dana joins, and her identity comes from **Supabase Auth**, which maps straight to her Mem0 `user_id`. She asks the questions every new hire asks and gets real answers on day one, drawn from the shared company (and her team's) brain, plus anything she's told it herself. + +```python +# onboarding.py +from brain import client, person +from ask import ask + +# In a real app these come from sb.auth.get_user(jwt) and the employees table. +dana_uid, dana_team = "8f3c...-dana", "payments" + +# Dana also tells the brain how *she* works. This is personal, so it goes to her +# user_id, not the shared agent, and stays scoped to her. +client.add( + [{"role": "user", "content": "I prefer early returns over nested ifs, and I review PRs in the morning."}], + user_id=person(dana_uid), +) + +for q in [ + "Who owns billing and who do I talk to?", # company (agent) knowledge + "When are deploys, and are there hard rules?", + "How do I like to write code?", # Dana's own (user) knowledge +]: + print(f"Q: {q}\nA: {ask(q, uid=dana_uid, user_team=dana_team)}\n") +``` + +```text +Q: Who owns billing and who do I talk to? +A: - Billing is owned by the Payments team; Alice is the Payments tech lead + +Q: When are deploys, and are there hard rules? +A: - The company's production deploys go out Tuesday and Thursday, never on Fridays + +Q: How do I like to write code? +A: - User prefers early returns over nested ifs +``` + +The same `ask()` blends the shared company facts with Dana's own preference, because the `OR` filter spans both her `user_id` and the org and team `agent_id`s. + +Dana onboarded herself by asking, drawing on the shared brain the rest of the team had been filling. + +## Production notes + + +**`user_id` vs `agent_id`.** An individual is a `user_id`; shared brains (company, team) are `agent_id`s. Keeping them separate is what gives you the third-person "The company…" framing and lets a person's own context sit alongside org knowledge. Put a secret like a webhook key on a **team** agent, never the company agent, or everyone can recall it, and mirror the boundary in Supabase with a Row Level Security policy on `knowledge`. + + + +**Search must `OR` the scopes.** A memory row belongs to exactly one entity, so `filters={"OR": [{"user_id": ...}, {"agent_id": "org:acme"}, {"agent_id": "team:..."}]}`. A flat filter, or an `AND` of a `user_id` and an `agent_id`, returns nothing. + + + +**`add()` is asynchronous.** It returns `{event_id, status: "PENDING"}` and extraction finishes a moment later, poll `GET /v1/event/{event_id}/` (as in Step 5) when you need to know a write has landed before searching for it. + + + +**Where the entity ID goes differs by call.** `search()` and `get_all()` take the scope inside `filters={...}` (a top-level `user_id=`/`agent_id=` is rejected). `add()` and `delete_all()` are the opposite, they take it as a top-level keyword: `client.delete_all(agent_id="team:payments")`. Deletes are asynchronous too, so a `get_all` right after a `delete_all` can still show rows for a few seconds. + + +## Where to take it next + +- **Auto-feed the brain** from PR descriptions, RFCs, and incident write-ups so it grows without anyone thinking about it, just insert into Supabase `knowledge` and let the cron ingest. +- **Scope by real identity** end to end: verify the Supabase JWT, read `sb.auth.get_user(jwt).user.id` for the `user_id`, look up the person's team, and `OR` their `user_id` with the company and team `agent_id`s on every recall. +- **Give teams a private view** with Supabase RLS so `team:` knowledge is only readable by that team. + +--- + + + + Connect any agent or editor to the brain over MCP. + + + Steer exactly what the brain extracts and how it's filed. + + + + diff --git a/docs/docs.json b/docs/docs.json index 4ca51fa24..7aecc007c 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -444,7 +444,8 @@ "cookbooks/integrations/mastra-agent", "cookbooks/integrations/healthcare-google-adk", "cookbooks/integrations/aws-bedrock", - "cookbooks/integrations/tavily-search" + "cookbooks/integrations/tavily-search", + "cookbooks/integrations/supabase" ] }, { diff --git a/docs/llms.txt b/docs/llms.txt index 686835a0e..ce60e29f5 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -326,6 +326,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or a Platform call st - [Healthcare Google ADK](https://docs.mem0.ai/cookbooks/integrations/healthcare-google-adk) [Platform]: Use when the domain is medical and the framework is Google ADK. - [AWS Bedrock](https://docs.mem0.ai/cookbooks/integrations/aws-bedrock) [OSS]: Use when deploying with AWS managed model services. - [Tavily Search](https://docs.mem0.ai/cookbooks/integrations/tavily-search) [Platform]: Use when the agent layers web search on memory. +- [Company Brain (Mem0 Platform + Supabase)](https://docs.mem0.ai/cookbooks/integrations/supabase) [Platform]: Use to build a shared org brain on Mem0 Platform with Supabase as system of record and the MCP server as the access layer (with a new-hire onboarding demo). ### Framework Examples - [LlamaIndex React](https://docs.mem0.ai/cookbooks/frameworks/llamaindex-react) [Both]: Use when building a React UI with LlamaIndex and memory.