集成与框架适配(2/6)

Add persistent memory to Hermes Agent with Mem0 Cloud, a self-hosted server, or the in-process OSS SDK.

Add long-term memory to Hermes Agent, a self-improving AI agent CLI by Nous Research. The standalone Mem0 plugin learns facts from conversations and recalls relevant memories for the current question.

You can run Mem0 in three ways:

  • Platform mode (default): managed Mem0 Cloud. Add your API key and you are ready.
  • Self-hosted server mode: point the plugin at a Mem0 server you run yourself (the Docker-shipped server). The plugin only talks HTTP to your server.
  • OSS mode: run Mem0 in-process with your own LLM, embedder, and vector store. No Mem0 server required.

How It Works

Hermes runs a built-in memory system (file-based MEMORY.md and USER.md) alongside one external provider. When Mem0 is active, it works additively with the built-in system at two points in every conversation turn.

1. Current-turn recall (bounded wait)

When you send a message, Hermes searches your stored memories for the current question and waits up to 3 seconds for results. If they arrive in time, they are injected into the system prompt so the model can see them. If the backend is slower, Hermes skips the injection and the model can still call mem0_search itself after the bounded recall wait.

2. Background fact extraction (sync)

Once the model finishes, the plugin sends the user message and assistant response to Mem0 in a background thread for fact extraction. Each write includes the agent identifier and gateway channel.

Automatic capture truncates each message to 450 characters by default in every mode, preferring a sentence boundary. Adjust sync_max_chars for your model's context limit. Capture is best effort: if the previous sync is still running after a five-second wait, the next turn is skipped. Use mem0_add to store specific text verbatim.

Agent Tools

When Mem0 is active, the model gets four tools it can call during a conversation:

Tool Description Parameters
mem0_search Semantic search by meaning, ranked by relevance query (required), top_k (default 10, max 50), rerank (uses the configured default, Platform mode only)
mem0_add Store a fact verbatim, with no LLM extraction content (required)
mem0_update Update a memory's text by ID memory_id, text (both required)
mem0_delete Delete a memory by ID memory_id (required)

Installation

Install Hermes Agent with memory-provider plugin support and Python 3.11 or later. Once the plugin directory is available on Mem0's main branch, install it from the repository subdirectory:

hermes plugins install mem0ai/mem0/integrations/hermes-plugin-mem0
hermes plugins enable mem0
hermes memory setup
hermes memory status

Select mem0 in setup and choose one of the modes below. Start a fresh Hermes conversation after setup.

Hermes installers with plugin dependency support install mem0ai>=2.0.10,<3 and httpx>=0.27,<1 from the plugin's pyproject.toml. Older hosts such as Hermes v0.21.3 require those packages to be installed explicitly into the Hermes Python environment. The OSS setup flow installs additional provider packages as needed.

Hermes versions that still bundle Mem0 prefer the bundled provider. Use a Hermes release that has completed the standalone-provider migration; installing this plugin alone does not replace the bundled implementation. Existing users should keep their current configuration; see Migration for existing users.

Run the setup wizard in an interactive terminal. On Hermes hosts whose hermes memory setup --help lists only a provider argument, options such as --mode, --host, and --oss-llm are rejected by Hermes before the plugin runs. Use hermes memory setup mem0 or the manual configuration below. Redirected input cannot select the mode picker and falls back to Platform.

Platform Setup

Platform mode uses managed Mem0 Cloud and is the fastest way to start.

hermes memory setup mem0

Choose Platform and paste your API key when prompted. The wizard writes settings to $HERMES_HOME/mem0.json and keeps the key in that profile's .env. The default Hermes home is ~/.hermes; named profiles use their own home directory.

Get your API key from https://app.mem0.ai?utm_source=oss&utm_medium=integration-hermes.

Option 2: Manual Configuration

hermes config set memory.provider mem0

Add your key to the active Hermes profile's .env:

MEM0_API_KEY=your-api-key

Set these values in the active profile's mem0.json, choosing a stable user identity:

{
  "mode": "platform",
  "host": "",
  "user_id": "my-hermes-user"
}

Remove any stale MEM0_HOST from the environment and profile .env, and remove an old inline api_key from mem0.json so the new .env key is used. The config command sets memory.provider: mem0 in that profile's config.yaml. Restart Hermes and check hermes memory status.

Self-Hosted Server Setup

Run the Mem0 server (FastAPI + pgvector) from its Docker image and point the plugin at it. Unlike OSS mode, the plugin just talks HTTP to your server.

Interactive

hermes memory setup mem0
# Choose "Self-hosted server", then enter the server URL and API key

Manual configuration

Select mem0 with hermes config set memory.provider mem0. Set these values in the active profile's mem0.json:

{
  "mode": "platform",
  "host": "http://localhost:8888",
  "user_id": "my-hermes-user"
}

Add the server key to that profile's .env:

MEM0_API_KEY=your-admin-api-key

Remove an old inline api_key from mem0.json so the .env key is used. MEM0_HOST can also supply the server URL, but a non-empty host in mem0.json overrides it. Keep mode set to platform for the HTTP server backend.

Then start a fresh Hermes session and call mem0_search — it connects to your server. The plugin authenticates with X-API-Key and uses the server's /search and /memories routes. The API key is optional only for servers running with AUTH_DISABLED.

Setting host routes to the self-hosted server automatically. Don't combine it with mode: oss — OSS takes precedence and ignores host.

OSS (Self-Hosted) Setup

OSS mode runs the Mem0 SDK in the Hermes process with your chosen LLM, embedder, and vector store. It does not use Mem0 Cloud or require a Mem0 API key. Data goes to the model services you configure; use local Ollama models and local storage for a fully local setup.

Interactive

hermes memory setup mem0
# Choose "Open Source"
# Follow the prompts for LLM, embedder, and vector store

The wizard uses the listed default OpenAI models and local Qdrant storage. For custom OpenAI-compatible endpoints, deployment names, or a Qdrant server, use manual configuration below.

Supported providers

Component Providers
LLM openai (default model gpt-5-mini), ollama (local, default llama3.1:8b)
Embedder openai (default text-embedding-3-small), ollama (local, default nomic-embed-text)
Vector store qdrant (local path or server), pgvector

Manual configuration

hermes config set memory.provider mem0

Add the model key to the active profile's .env:

OPENAI_API_KEY=your-model-api-key

Set the following in that profile's mem0.json. Use your existing storage path when migrating; for a new named profile, choose a path inside that profile's home.

{
  "mode": "oss",
  "user_id": "my-hermes-user",
  "oss": {
    "llm": {"provider": "openai", "config": {"model": "gpt-5-mini", "is_reasoning_model": true}},
    "embedder": {"provider": "openai", "config": {"model": "text-embedding-3-small"}},
    "vector_store": {"provider": "qdrant", "config": {"path": "~/.hermes/mem0_qdrant"}}
  }
}

For an OpenAI-compatible service such as Azure's /openai/v1 endpoint, add OPENAI_BASE_URL to the profile's .env and set each model to its deployed name. Both the LLM and embedder use this endpoint unless their config.openai_base_url overrides it. The main Hermes chat model is configured separately; this JSON configures Mem0's extraction and embedding models.

For a Qdrant server, replace vector_store.config.path with url, for example "url": "http://localhost:6333". Manual setup does not install optional provider dependencies: install qdrant-client, psycopg2-binary, or ollama in the Hermes Python environment as needed for your selected providers. Start a fresh session and verify a memory write and search; hermes memory status reports configuration availability, not a full backend health check.

Desktop sessions in the same process and profile share local Qdrant storage when their OSS settings match. Operations are serialized, and storage closes after the last session releases it. Conflicting settings are rejected without changing existing memories; close active sessions before changing models or credentials. For concurrent CLI and Desktop processes, use a Qdrant server or the self-hosted Mem0 HTTP API instead of sharing a local directory.

Switching Modes

Run hermes memory setup mem0 in an interactive terminal and choose the new mode, or edit the active profile's mem0.json using the examples above. Switching backends does not transfer memories between them. Preserve existing OSS storage paths when editing configuration. When returning to Platform, set mode to platform, clear host, and remove any stale MEM0_HOST setting from your environment and profile .env.

Configuration

Settings live in $HERMES_HOME/mem0.json and are written by hermes memory setup. API keys normally live in that profile's .env; distinct OpenAI LLM/embedder keys and database credentials are stored in the OSS configuration. Setup writes these files atomically with owner-only permissions.

When editing these files manually, restrict both .env and mem0.json to their owner (chmod 600 on Unix). Keep configuration and secrets in the same active Hermes profile.

MEM0_MODE, MEM0_HOST, MEM0_USER_ID, and MEM0_AGENT_ID supply environment defaults. Non-empty values in mem0.json take precedence. MEM0_API_KEY supplies the Cloud or server key unless api_key is set in the file.

Key Default Description
mode platform platform (Mem0 Cloud) or oss (self-managed, in-process). Self-hosted server routing is set via host
host none Self-hosted Mem0 server URL. When set, the plugin talks HTTP to your server instead of the cloud
api_key none Mem0 Platform API key, or the admin key of a self-hosted server. Stored in .env as MEM0_API_KEY
user_id gateway user ID, then hermes-user Identifier that scopes memories. See cross-channel behavior below
agent_id hermes Agent identifier attached to writes
rerank false Platform reranking for recall and tool searches that omit rerank
sync_max_chars 450 Per-message character cap for automatic fact extraction in every mode
oss {} OSS LLM, embedder, and vector-store configuration

Cross-channel memories

Hermes can run from the CLI and from gateways like Telegram, Slack, and Discord. The user_id setting controls how memories are scoped across them:

  • Set a user_id other than hermes-user and it applies to every gateway, so one person gets a single merged memory store no matter where they talk to the agent.
  • Leave it unset (or at the default hermes-user) and each gateway uses its own native ID when available, falling back to hermes-user.

Every write is tagged with metadata.channel (for example telegram or cli). Plugin searches filter by user identity across sessions; they do not restrict recall to the current channel or session.

Migration for Existing Users

Keep memory.provider: mem0, mem0.json, MEM0_* settings, user identity, and OSS database paths unchanged. Moving from the bundled provider to this standalone plugin does not require rerunning setup or moving stored memories.

Automatic migration depends on Hermes rollout as well as this repository:

  1. Users need a Hermes build containing PR #114569.
  2. Hermes maintainers must approve a catalog entry named mem0 with repo: https://github.com/mem0ai/mem0, subdir: integrations/hermes-plugin-mem0, and a reviewed full commit SHA.
  3. The bundled Mem0 provider must be removed so the standalone provider can load.

With these in place, Hermes installs a missing configured provider during hermes update across profiles or at agent startup. Startup installation respects security.allow_lazy_installs. Offline or disabled installation needs manual action; merging the plugin directory alone does not complete automatic migration.

CLI setup and status are supported. This plugin does not ship a Desktop configuration panel or provider-specific CLI commands.

Reliability

  • Circuit breaker: five consecutive backend failures pause calls for two minutes. The agent can continue without memory during that window. Expected update/delete errors such as a missing memory do not trip the breaker.
  • Bounded waits: recall waits up to three seconds. Capture runs in the background, but an overlapping turn may wait up to five seconds for the previous sync before being skipped.
  • Graceful shutdown: shutdown and Python process exit wait for active recall and capture workers before closing the backend. Backend network timeouts still apply. Self-hosted HTTP capture uses a 120-second read timeout and a 30-second connection timeout; other self-hosted HTTP operations use 30 seconds.
  • Best-effort capture: there is no durable queue. Forced termination, including Hermes' 30-second exit watchdog, can interrupt pending writes even while graceful shutdown is waiting.
  • OSS data protection: an embedding dimension mismatch fails initialization without deleting the existing collection or table.

Troubleshooting

"Mem0 temporarily unavailable"

The circuit breaker tripped after five consecutive failures and resets after two minutes.

  • Platform mode: check your API key and internet connection.
  • Self-hosted server mode: check that the server is running and reachable at the configured host URL.
  • OSS mode: make sure your vector store (Qdrant or PGVector) is running and reachable.

OSS: vector store connection refused

# Local Qdrant: confirm the storage path is writable
ls -la ~/.hermes/mem0_qdrant

# Qdrant server: confirm it is reachable
curl http://localhost:6333/healthz

# PGVector: confirm PostgreSQL is accepting connections
pg_isready -h localhost -p 5432

OSS: Ollama not reachable

curl http://localhost:11434/api/tags

Memories not appearing

  • mem0_add stores text verbatim with no extraction. Ordinary conversation turns are extracted automatically by the background sync.
  • Search is semantic, so try a broader query.
  • Confirm user_id is the same across sessions (check $HERMES_HOME/mem0.json).
  • Check sync_max_chars: facts beyond the per-message limit are not sent for extraction.

OSS: embedding dimension mismatch

Restore the embedding model and dimensions that created the existing collection, or choose a new collection and migrate data explicitly. The plugin leaves the original collection intact when dimensions differ.

Add memory to OpenClaw agents with auto-recall and auto-capture



Get your API key and explore the Mem0 dashboard

<Snippet />


Build a shared company brain using Mem0 Platform as the managed memory layer, Supabase as your system of record, and 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.)

Architecture

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

About 20 minutes.


Step 1: Get your Mem0 Platform API key

Sign in at 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.

-- 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

mkdir company-brain && cd company-brain
pip install "mem0ai>=2.0.17" supabase requests   # 2.0.17+ for agent_custom_instructions
export MEM0_API_KEY="m0-..."
export SUPABASE_URL="https://<project-ref>.supabase.co"
export SUPABASE_SERVICE_KEY="<service-role-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.

# 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:

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.

# 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.")
python ingest.py
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.

# 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?"))
python ask.py "Why did we pick Postgres, and can I deploy on Friday?"
- 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:

python ask.py "Who should I talk to about billing?"
- 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:

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:

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.

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.
{
  "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).
# 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.

# 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")
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_ids.

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_ids. 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_ids 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.

<Snippet />