Self-host GBrain, shared memory for your agents
One memory for every agent you run, behind two locks.
Shelley handles the setup. About 10 minutes.
Prefer a terminal?
curl -fsSL https://exe.dev/deploy/gbrain/prompt.txt | ssh exe.dev new --prompt=/dev/stdinGBrain is a memory for AI agents: they save facts, people, decisions and notes into it and search it before they answer. On one computer, each agent keeps its own memory. Served over MCP, one brain is shared: tell Claude Code something on your laptop, and Codex, OpenClaw, and Hermes know it too.
That needs a server that's always on, at an HTTPS address, and not open to the world. An exe.dev VM is all three. GBrain runs there on PostgreSQL, and every request passes two locks: exe.dev's proxy lets in only you and holders of a VM token, then GBrain checks the agent's own key. Each agent gets its own key, so you can see who wrote what and cut one off without touching the rest.
What you get#
- GBrain's MCP server at
https://my-gbrain.exe.xyz/mcpon PostgreSQL, asgbrain.service, restarted on failure and after a reboot. - Two locks on every request: exe.dev lets in only your login or a VM token, then GBrain checks a key that belongs to one agent.
- A read-write GBrain key for each agent you named, revocable one at a time.
- The admin dashboard at
/admin, and an owner token kept in~/gbrain/.env. - Nightly backups of the database and settings in
~/backups, with a restore tested.
What's running, and where
https://my-gbrain.exe.xyz/mcp
the MCP endpoint; agents send an exe.dev VM token and their own GBrain key
https://my-gbrain.exe.xyz/admin
GBrain's dashboard, behind your exe.dev login and GBrain's owner token
gbrain serve --http on Bun, as gbrain.service: MCP, OAuth and the admin dashboard
Deploy with Shelley#
Click Deploy and Shelley will set up GBrain on a new VM, then check that it works. No config-file wrangling required.
Have these ready; Shelley asks for them:
AGENTS: the agents that will share the brain, as short names such aslaptop-claude, laptop-codex, openclaw, hermes; Shelley makes a GBrain key for each
Prefer a terminal?
curl -fsSL \
https://exe.dev/deploy/gbrain/prompt.txt \
| ssh exe.dev new --prompt=/dev/stdinWhen it's done, run this on your computer (only you can):
ssh exe.dev ssh-key generate-api-key --vm=my-gbrain --label=gbrain-agents --exp=90dWhat Shelley is told
AGENTS=<fill-this-in>
Set up GBrain on this VM by following the exe.dev recipe at https://exe.dev/deploy/gbrain.md from top to bottom.
The values at the top of this message are mine:
- AGENTS: the agents that will share the brain, as short names such as `laptop-claude, laptop-codex, openclaw, hermes`; Shelley makes a GBrain key for each
Ask me for any that still say <fill-this-in> before you need them.
The recipe's example VM name is my-gbrain. This VM's name is the output of `hostname`: use it wherever the recipe writes my-gbrain. Skip the step that creates the VM; you are on it.
You are done when all of these are true. Check each one yourself:
- GBrain's MCP server at `https://my-gbrain.exe.xyz/mcp` on PostgreSQL, as `gbrain.service`, restarted on failure and after a reboot.
- Two locks on every request: exe.dev lets in only your login or a VM token, then GBrain checks a key that belongs to one agent.
- A read-write GBrain key for each agent you named, revocable one at a time.
- The admin dashboard at `/admin`, and an owner token kept in `~/gbrain/.env`.
- Nightly backups of the database and settings in `~/backups`, with a restore tested.
Read the whole recipe, closed details included, and follow "Set up by
hand" in order, skipping VM creation; the walkthrough above it is for
the owner. Ask for AGENTS if it still says <fill-this-in>. Make one key
per agent with `gbrain auth create <name> --scopes read,write`, using
short lowercase names. Never make the VM public or bind Postgres beyond
127.0.0.1. On an existing VM, ask before replacing anything on port 8000
or 5432. Don't reboot. Test the backup in a scratch directory only; never
restore over the live database. The VM can't reach its own
https://<vm>.exe.xyz, so checks use localhost.
Finish with the MCP URL, each agent's key, the owner token from
`~/gbrain/.env` for /admin, and the `ssh-key generate-api-key` command
for the owner to run on their computer, then point them to "Connect
your agents". This conversation is private to the owner.
Finish with the URLs I should open and these commands, which only I can run, from my own computer (with this VM's name in them):
ssh exe.dev ssh-key generate-api-key --vm=my-gbrain --label=gbrain-agents --exp=90d
Also at /deploy/gbrain/prompt.txt. The recipe it follows is /deploy/gbrain.md.
Connect your agents#
Each agent sends two headers:
X-Exedev-Authorization: Bearer exe1.…, a VM token that gets it through exe.dev's proxy, which removes it before GBrain sees the request.Authorization: Bearer gbrain_…, the agent's GBrain key.
Make the VM token on your computer (--exp sets when it expires; one
token can serve all your agents, or make one per machine):
ssh exe.dev ssh-key generate-api-key --vm=my-gbrain --label=gbrain-agents --exp=90d
Shelley made a GBrain key for each agent you named. To make another, on the VM:
gbrain auth create laptop-claude --scopes read,write
It prints the key once. Below, replace exe1.paste-yours-here and
gbrain_paste-yours-here with yours.
Claude Code, on your computer or a VM like the one in
Claude Code on exe.dev (saves both in ~/.claude.json):
claude mcp add --transport http --scope user gbrain https://my-gbrain.exe.xyz/mcp \
--header "X-Exedev-Authorization: Bearer exe1.paste-yours-here" \
--header "Authorization: Bearer gbrain_paste-yours-here"
claude mcp list
Expect gbrain: https://my-gbrain.exe.xyz/mcp (HTTP) - ✔ Connected.
Codex, on your computer or a Codex VM, in
~/.codex/config.toml:
[mcp_servers.gbrain]
url = "https://my-gbrain.exe.xyz/mcp"
http_headers = { "X-Exedev-Authorization" = "Bearer exe1.paste-yours-here", "Authorization" = "Bearer gbrain_paste-yours-here" }
OpenClaw, on its VM (from Self-host OpenClaw). It checks the connection before saving:
openclaw mcp add gbrain --url https://my-gbrain.exe.xyz/mcp --transport streamable-http \
--header "X-Exedev-Authorization=Bearer exe1.paste-yours-here" \
--header "Authorization=Bearer gbrain_paste-yours-here"
openclaw gateway restart
Hermes, on its VM (from Self-host Hermes Agent).
The headers name variables that Hermes keeps in ~/.hermes/.env:
hermes config set mcp_servers.gbrain.url https://my-gbrain.exe.xyz/mcp
hermes config set mcp_servers.gbrain.headers.X-Exedev-Authorization 'Bearer ${EXE_GBRAIN_TOKEN}'
hermes config set mcp_servers.gbrain.headers.Authorization 'Bearer ${MCP_GBRAIN_API_KEY}'
hermes config set EXE_GBRAIN_TOKEN exe1.paste-yours-here
hermes config set MCP_GBRAIN_API_KEY gbrain_paste-yours-here
hermes mcp test gbrain
Expect a list of GBrain's tools. Restart the gateway
(systemctl --user restart hermes-gateway) so scheduled jobs and chat
apps get them too.
Other clients: any MCP client that speaks Streamable HTTP and can send headers works the same way.
Tip
For agents on your other exe.dev VMs, a VM-to-VM integration can add both locks at exe.dev's edge, so neither token is stored on the agent's VM. On your computer, for an agent on
my-hermes-agent:ssh exe.dev integrations add http-proxy --name gbrain-hermes --target https://my-gbrain.exe.xyz/ --peer --bearer gbrain_paste-yours-here --attach vm:my-hermes-agent. Then the agent's URL ishttps://gbrain-hermes.int.exe.xyz/mcp, with no headers.
Try it#
Tell Claude Code:
Remember with gbrain: the team offsite is in Lisbon on November 20.
Then ask Codex, OpenClaw, or Hermes:
What do you know about the team offsite?
They find it in the brain, with where it came from. Hermes can add to it ("the hotel is the Memmo Alfama"), and the others see that next.
Agents use memory more when they're told to. Add a line like this to
CLAUDE.md, AGENTS.md, or Hermes's SOUL.md:
Before answering, search gbrain for what you already know. When I tell you a lasting fact or decision, save it to gbrain with where it came from.
Manage access#
On the VM, list the keys, with when each was last used, and revoke one:
gbrain auth list
gbrain auth revoke laptop-claude
A revoked key gets 401 Invalid token at once. --scopes read makes a
key that can search but not save. On your computer, ssh exe.dev ssh-key list shows VM tokens, and ssh exe.dev ssh-key remove takes one away.
The dashboard is at https://my-gbrain.exe.xyz/admin: open it signed
in to exe.dev, then paste the owner token, which is in ~/gbrain/.env on
the VM:
grep GBRAIN_ADMIN_BOOTSTRAP_TOKEN ~/gbrain/.env
It shows live requests, the agents and their keys, and makes and revokes keys. Keep the owner token to yourself; agents never need it.
Cloud agents#
claude.ai, ChatGPT, and agents that run in their makers' clouds can't send
an exe.dev token, so they can only reach a public VM. GBrain then is the
only lock. It's a real one: /mcp refuses requests without a key or
OAuth token, and every OAuth connection waits for you to approve it in
the dashboard. To open it, on your computer:
ssh exe.dev share set-public my-gbrain
Then follow GBrain's guide for the app, such as
ChatGPT,
with https://my-gbrain.exe.xyz/mcp as the URL. Agents with keys keep
working, and can drop the exe.dev header.
Warning
Public means the dashboard's sign-in page and OAuth endpoints face the internet. Keep the owner token secret, approve only connections you just started, and go back with
ssh exe.dev share set-private my-gbrainwhen you're done.
The nuts and bolts#
Happy with what Shelley set up? Stop here. Below are the steps it followed, and maintenance for later.
Set up by hand
Create the VM#
On your computer (pick another name if my-gbrain is taken):
ssh exe.dev new --name=my-gbrain
ssh my-gbrain.exe.xyz
Everything else runs on the VM, as exedev.
Install Bun and GBrain#
GBrain runs on Bun. Install it, then GBrain's stable release:
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
bun install -g github:garrytan/gbrain#latest-stable
gbrain --version
Expect gbrain 0.60 or later. Bun notes that it blocked a postinstall
script; that's fine on a new install.
Start PostgreSQL#
Generate the database password and GBrain's owner token into one private file, and run PostgreSQL with pgvector on localhost only:
mkdir -p ~/gbrain && cd ~/gbrain
if [ ! -e .env ]; then
install -m 600 /dev/null .env
cat >> .env <<EOF
# Secrets for PostgreSQL and gbrain.service. Back this file up.
POSTGRES_PASSWORD=$(openssl rand -hex 24)
GBRAIN_ADMIN_BOOTSTRAP_TOKEN=$(openssl rand -hex 32)
EOF
fi
cat > compose.yaml <<'EOF'
services:
postgres:
image: pgvector/pgvector:pg16
restart: unless-stopped
environment:
POSTGRES_USER: gbrain
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: gbrain
ports:
- "127.0.0.1:5432:5432"
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "gbrain"]
interval: 5s
retries: 20
EOF
sudo systemctl enable --now docker.service
docker compose up -d --wait
Expect Container gbrain-postgres-1 Healthy.
Create the brain#
Point GBrain at the database. --no-embedding starts it keyless, with
keyword search:
. ~/gbrain/.env
gbrain init --url "postgresql://gbrain:$POSTGRES_PASSWORD@127.0.0.1:5432/gbrain" \
--no-embedding --non-interactive
It ends with suggestions for wiring a local agent; skip those, since
agents connect over HTTP here. The configuration is in ~/.gbrain.
Run the server#
exe.dev's proxy forwards https://<vm>.exe.xyz/ to port 8000, so GBrain
listens there, on all interfaces, with that address as its public URL
(for OAuth and dashboard links):
sudo tee /etc/systemd/system/gbrain.service >/dev/null <<EOF
[Unit]
Description=GBrain MCP server (gbrain serve --http)
After=network-online.target docker.service
Wants=network-online.target docker.service
[Service]
User=exedev
WorkingDirectory=/home/exedev
EnvironmentFile=/home/exedev/gbrain/.env
Environment=PATH=/home/exedev/.bun/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/exedev/.bun/bin/gbrain serve --http --port 8000 --bind 0.0.0.0 --public-url https://$(hostname).exe.xyz
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now gbrain
Its log warns that GBRAIN_HTTP_CORS_ORIGIN is unset. That only matters
for MCP clients running in a web page; leave it.
Check it#
Check health, that /mcp refuses a request without a key, and that a
key works (a throwaway one, revoked at the end):
sleep 5
curl -s http://localhost:8000/health; echo
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:8000/mcp
key=$(gbrain auth create setup-check --scopes read | grep -o 'gbrain_[0-9a-f]*')
curl -s -X POST http://localhost:8000/mcp -H "Authorization: Bearer $key" \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"1"}}}' |
grep -o '"serverInfo":{[^}]*}'
gbrain auth revoke setup-check
Expect "status":"ok" with "engine":"postgres", then 401, then
"serverInfo":{"name":"gbrain",...}.
Then make a key for each agent, as in Connect your agents:
gbrain auth create laptop-claude --scopes read,write
Back up nightly#
This script dumps the database with pg_dump (safe while GBrain runs)
and adds ~/.gbrain and ~/gbrain's compose.yaml and .env. It keeps
the 14 newest:
mkdir -p ~/bin ~/backups
cat > ~/bin/gbrain-backup <<'EOF'
#!/bin/bash
# Back up gbrain to ~/backups/gbrain-<time>.tar.gz: a pg_dump of the
# database (safe while the server runs), ~/.gbrain, and ~/gbrain's
# compose.yaml and .env. Keeps the 14 newest.
set -euo pipefail
umask 077
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
mkdir -p "$HOME/backups"
docker compose -f "$HOME/gbrain/compose.yaml" exec -T postgres \
pg_dump -U gbrain -Fc gbrain < /dev/null > "$work/gbrain.dump"
out="$HOME/backups/gbrain-$(date -u +%Y%m%d-%H%M%S).tar.gz"
tar -czf "$out.part" -C "$work" gbrain.dump \
-C "$HOME" --exclude=.gbrain/run .gbrain gbrain/compose.yaml gbrain/.env
mv "$out.part" "$out"
ls -1t "$HOME"/backups/gbrain-*.tar.gz | tail -n +15 | xargs -r rm --
echo "$out"
EOF
chmod +x ~/bin/gbrain-backup
Run it nightly, around 03:30 UTC:
sudo tee /etc/systemd/system/gbrain-backup.service >/dev/null <<'EOF'
[Unit]
Description=Back up gbrain to ~/backups
[Service]
Type=oneshot
User=exedev
ExecStart=/home/exedev/bin/gbrain-backup
EOF
sudo tee /etc/systemd/system/gbrain-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Back up gbrain nightly
[Timer]
OnCalendar=*-*-* 03:30:00 UTC
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now gbrain-backup.timer
Make one now and check that it restores, into a scratch database:
backup=$(~/bin/gbrain-backup)
rm -rf /tmp/restore-check && mkdir /tmp/restore-check
tar -xzf "$backup" -C /tmp/restore-check
ls -A /tmp/restore-check
cd ~/gbrain
docker compose exec -T postgres createdb -U gbrain restore_check < /dev/null
docker compose exec -T postgres pg_restore -U gbrain -d restore_check < /tmp/restore-check/gbrain.dump
docker compose exec -T postgres psql -U gbrain -d restore_check -tAc 'select count(*) from access_tokens' < /dev/null
docker compose exec -T postgres dropdb -U gbrain restore_check < /dev/null
rm -rf /tmp/restore-check
Expect .gbrain gbrain gbrain.dump, then the number of keys made so far.
Copy ~/backups off the VM now and then; otherwise the VM's disk holds
the only copy.
Back up and restore
On the VM, ~/bin/gbrain-backup backs up now and prints the archive's
path. Each holds gbrain.dump (the whole database: memories, keys, OAuth
clients), .gbrain/ (GBrain's configuration), and gbrain/.env (the
database password and owner token). To copy them to your computer:
scp 'my-gbrain.exe.xyz:backups/gbrain-*.tar.gz' .
Restore#
On a new VM, first follow Set up by hand through "Install Bun and GBrain", then copy the archive over from your computer:
scp gbrain-20261030-033012.tar.gz my-gbrain.exe.xyz:
On the VM (or on the old one, to roll back), stop GBrain, put the settings back, start PostgreSQL, and load the dump over whatever is there:
backup=~/gbrain-20261030-033012.tar.gz
sudo systemctl stop gbrain 2>/dev/null || true
[ -d ~/.gbrain ] && mv ~/.gbrain ~/.gbrain.before-restore-$(date +%s)
rm -rf /tmp/restore && mkdir /tmp/restore && tar -xzf "$backup" -C /tmp/restore
mkdir -p ~/gbrain
install -m 600 /tmp/restore/gbrain/.env ~/gbrain/.env
cp /tmp/restore/gbrain/compose.yaml ~/gbrain/
cp -a /tmp/restore/.gbrain ~/
sudo systemctl enable --now docker.service
cd ~/gbrain && docker compose up -d --wait
docker compose exec -T postgres pg_restore -U gbrain -d gbrain --clean --if-exists < /tmp/restore/gbrain.dump
rm -rf /tmp/restore
On a new VM, continue with Run the server and the
backup timer; on the old one, sudo systemctl start gbrain. Then
check it. Agents keep their keys, since those are in the
database; on a VM with another name, point them at its URL and make them
a VM token for it.
Update GBrain, Bun, and PostgreSQL
On the VM. GBrain won't upgrade under a running server it didn't start, so stop it first. The two variables keep the upgrade from installing GBrain's own background services or re-embedding anything:
backup=$(~/bin/gbrain-backup)
sudo systemctl stop gbrain
GBRAIN_NO_AUTOPILOT_INSTALL=1 GBRAIN_NO_REEMBED=1 gbrain upgrade --no-autopilot-install
sudo systemctl start gbrain
gbrain --version
It applies database migrations too. If it says the new release needs a
newer Bun, run bun upgrade and the upgrade again. Read the
changelog
first if you're far behind.
- PostgreSQL:
cd ~/gbrain && docker compose pull && docker compose up -d --waitupdates within 16. A new major version needs a dump and restore. - Ubuntu:
sudo apt-get update && sudo apt-get upgrade -y.
Something not working?
On the VM, GBrain's and PostgreSQL's logs:
journalctl -u gbrain -n 100 --no-pager
systemctl status gbrain --no-pager
cd ~/gbrain && docker compose logs --tail 50 postgres
401 invalid or missing authentication, or an exe.dev login page. The
VM token didn't reach exe.dev: check the header name
(X-Exedev-Authorization), the Bearer before it, and that the token is
for this VM and unexpired.
401 Missing Authorization header or Invalid token. exe.dev let the
request in and GBrain refused it. The Authorization: Bearer gbrain_…
header is missing, or the key was revoked (gbrain auth list).
502 Bad Gateway. Nothing answers on port 8000: the service is
stopped or failed at start (see its log), often because PostgreSQL isn't
up. curl -s localhost:8000/health on the VM tells you which.
OpenClaw says SSE error: Non-200 status code (405). Add
--transport streamable-http; GBrain doesn't serve the older SSE
transport.
gbrain commands fail with pglite_busy. The brain is on PGLite, not
PostgreSQL: ~/.gbrain/config.json should say "engine": "postgres".
Rerun Create the brain with --force.
Agents never use the brain. Check claude mcp list (or hermes mcp test gbrain, openclaw mcp probe) shows it connected, then add the
instruction from Try it. In Claude Code, /mcp lists its tools.
Frequently asked questions#
What does it cost to self-host GBrain?
GBrain is MIT-licensed, so just your VM plan. Memory works without any model key: agents save facts and GBrain finds them by keyword. Optional features such as semantic search and automatic fact extraction call a model provider and bill there.
Who can reach my brain?
Only callers that pass both locks: exe.dev's proxy admits you (signed in) and anything holding a VM token for this VM, and GBrain then wants a key of its own. A leaked GBrain key is useless without a VM token, and the reverse. Revoke either on its own.
What about `gbrain mcp expose`?
It gives a brain on your own computer an HTTPS address through a tunnel and keeps the server running. A VM already has an HTTPS address with a login in front, so this page runs the same gbrain serve --http that expose would, as a systemd service, with --public-url https://my-gbrain.exe.xyz.
Why PostgreSQL instead of the default PGLite?
PGLite lets one process open the brain at a time. With the server running, gbrain commands on the VM (making keys, searching, importing notes) are refused. PostgreSQL takes them all at once, and pg_dump backs it up while it runs.
Can claude.ai or ChatGPT use it?
Only if you make the VM public, since they connect from their makers' servers. GBrain then is the only lock: OAuth with your approval for each app, and its keys. See Cloud agents.
Does every agent see every memory?
By default, yes: that's the point of a shared brain. GBrain can split memories into sources and limit a key to some of them; see its administration guide.
Updated Oct 30, 2026 · tested end to end Oct 30, 2026 · Markdown · manifest.json · Something wrong? Tell us