Memmy: a local memory hub for agents on a VPS
Memmy gives your AI agents one shared memory store. Build it from source on Ubuntu, run the memory service on port 18960, and keep every note local.
What Memmy is and what it stores
Memmy is a local memory hub for AI agents that runs on your own VPS (virtual private server). It keeps one SQLite database of what your agents have learned, and every agent on the box reads and writes that same store. The project is memmy-agent from MemTensor, MIT licensed, at version 1.0.4 as of July 2026.
Only part of it matters on a server. Memmy ships a memory service that listens on http://127.0.0.1:18960, a memmy-memory command line interface (CLI) that talks to that service, and a desktop workbench. The workbench is packaged for macOS and Windows only, so on a Linux VPS you run the service and the CLI. That is enough to give Claude Code, Codex and Cursor a shared memory.
Memmy sorts what it stores into four layers. L1 Trace is the raw turn: the request, the response and the tool calls. L2 Policy is a procedure induced from traces that proved useful. L3 World Model is stable knowledge about a project or an environment. Skill is a callable procedure crystallised from a policy. The service assigns a layer when it ingests a turn, so you do not create them by hand.
What a shared memory hub changes compared to per-tool memory
Every agent today ships its own memory. Claude Code keeps instruction files in the repository. Cursor keeps rules in its workspace database. Codex keeps session logs under ~/.codex. Each store belongs to one tool, so a fact you taught on Monday in one tool is unknown on Tuesday in another. You pay for that twice: once in tokens spent re-explaining the same project, and once in wrong work when an agent acts on an assumption you already corrected somewhere else.
A hub moves the store out of the tool. Memmy also reads the existing stores, so you do not start from an empty database. Its scanner knows six sources: Claude Code at ~/.claude/projects/**/*.jsonl, Codex at ~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl, OpenCode at ~/.local/share/opencode/opencode.db, Cursor's state.vscdb files, OpenClaw's SQLite databases under ~/.openclaw, and Hermes under ~/.hermes. You can add a source by hand with a name and a local path.
The import counters will not line up, and that is expected. The scanner groups messages by source and conversation, then writes one L1 memory per complete turn. A turn counts as complete when it has non-empty user content and ends with a non-empty assistant message, so an interrupted session contributes nothing. Messages are deduplicated with conversation checkpoints and stable turn IDs. The scanned count, the imported message count and the new memory count all differ on the same run.
This is the piece that pairs with how Claude Code manages context inside one session. Context management decides what fits in a single window. A memory hub decides what survives after that window closes.
What you need on the VPS
- Node.js 22 or newer. The Memmy docs require it, and Ubuntu 24.04 ships Node 18.
gitand a build toolchain, becausebetter-sqlite3is a native module that may compile during install.- About 2 GB of RAM. The root install pulls a large workspace and a frontend build chain.
- A few GB of free disk for
node_modulesand the database.
sudo apt update
sudo apt install -y git build-essential python3 curl ca-certificates sqlite3
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --versionnode --version should print v22 or higher. A v18 here means the NodeSource step did not take, and the install will later fail on the project's engine check.
Install Memmy from source on Ubuntu 24.04
git clone https://github.com/MemTensor/memmy-agent.git
cd memmy-agent
cp .env.example .env
npm install
npm run memory:buildnpm run memory:build compiles the @memmy/memory workspace into Memory/dist. Nothing else in the tree needs to build for a headless server. Check that the native module loaded:
node -e "require('better-sqlite3'); console.log('better-sqlite3 loads')"If that line throws instead of printing, the native module does not match your Node version. Run npm rebuild better-sqlite3, which is exactly what the project's own start script does before it launches anything.
The README documents bash scripts/dev-start.sh as a one command start. Do not run it on a headless VPS. It starts the Electron desktop shell and a Vite dev server on port 19000 next to the memory service, and Electron needs a display, so on a server with no graphical session the script stalls or exits.
Start the memory service and check that it answers
npm run memory:serve:devThat is the documented way to run the memory service from source. It binds 127.0.0.1:18960, keeps the database at ~/.memmy/memory-service/memory.sqlite, and reads config from ~/.memmy/config.yaml. The README spells the same values out when you want them explicit:
npm run memory:serve:dev -- \
--host 127.0.0.1 --port 18960 \
--db ~/.memmy/memory-service/memory.sqlite \
--config ~/.memmy/config.yamlFrom a second shell, ask the service whether it is alive:
curl -sS http://127.0.0.1:18960/api/v1/healthHealth is the one endpoint that never asks for a token, which is why it is the right probe. If curl exits with code 7 and a Failed to connect to 127.0.0.1 port 18960 message, nothing is listening. Read the terminal running the service, because a crash at startup prints there, and the usual cause is the native SQLite module failing to load. ss -lntp | grep 18960 confirms the socket once the service is up.
The rest of the HTTP API (application programming interface) sits under /api/v1.
POST /api/v1/memory/addwrites a memory andPOST /api/v1/memory/searchqueries.GET /api/v1/memory/:idandDELETE /api/v1/memory/:idread and remove one entry.POST /api/v1/sessions/openandPOST /api/v1/sessions/:sessionId/closebracket an agent session.POST /api/v1/turns/startandPOST /api/v1/turns/:turnId/completerecord one turn.GET /api/v1/panel/overview,/api/v1/panel/analysisand/api/v1/panel/itemsfeed the dashboard.
Memmy reserves a block of ports, and headless you use only the first: 18960 for memory, 18970 for gateway health, 18980 for the web UI and admin HTTP, 18990 for the OpenAI-compatible API that memmy serve starts, then 19000 and 19010 for the desktop frontend's dev server. If something on your box already holds one of those, that list is where to look.
Where the memmy-memory command actually comes from
This is where a first install usually goes wrong, so read it from the package instead of guessing. The command name has nothing to do with the repository name. It comes from the bin field of the workspace that defines it:
node -p "JSON.stringify(require('./Memory/package.json').bin)"That prints {"memmy-memory":"./dist/src/cli/index.js"}. So the built entry point is Memory/dist/src/cli/index.js, and it exists only after npm run memory:build, because the build is what creates dist and marks the file executable. Run it directly:
node Memory/dist/src/cli/index.js healthIf you want the short name on your PATH, link that same file:
sudo ln -s "$PWD/Memory/dist/src/cli/index.js" /usr/local/bin/memmy-memory
memmy-memory healthThe CLI defaults to http://127.0.0.1:18960 and accepts --url, --token, --config, --source and --user-id. Its subcommands are init, health, search, add, get and delete, plus session and turn calls that agents use rather than people. memmy-memory search "deploy steps" and memmy-memory add "staging migrates on deploy" are the two an agent runs most.
How do you connect Claude Code to Memmy?
Claude Code has no memory plugin interface, so Memmy does not hook into it. The integration is plainer than that. Claude Code runs memmy-memory as an ordinary shell command, and an instruction file tells it when to. Memmy's documented installer writes that file for you: memmy-memory init --agent drops a memory instruction file into the target agent's rules directory.
Write the instruction by hand once, because then you know exactly what the agent was told. Claude Code reads CLAUDE.md from the project root at the start of every session, so a section like this is the whole integration:
## Memory
Before starting a task, run `memmy-memory search "<topic>"` and read what comes back.
When a task is done, run `memmy-memory add "<what you learned>"` for anything that will matter next session.Be clear about what that buys you. It is instruction level integration, so it works when the model decides to run the command, and not otherwise. Nothing forces the call. If a session ends without the add, nothing was saved, and the only signal is an empty result the next time you search. That is the same trade-off as Claude Code's own memory files, with one difference: the store is shared, so the note also reaches Codex and Cursor on the same machine.
The other direction needs no setup at all. Memmy's scanner already reads ~/.claude/projects/**/*.jsonl, which is where Claude Code writes its session transcripts. Run Memmy on the same server where you run Claude Code inside a tmux session and yesterday's work becomes memory without you configuring anything.
Does Memmy work as an MCP server for Claude Code?
No, and knowing the direction saves an afternoon. MCP (model context protocol) has clients and servers. Memmy is a client. It connects out to MCP servers and offers their tools to its own agent runtime. It does not publish an MCP endpoint that claude mcp add can point at. The only MCP bridge in the repository belongs to the Composio integration inside the desktop local API, and that API binds a random port on 127.0.0.1 behind its own x-memmy-mcp-token header.
The client side is configured in ~/.memmy/config.yaml, the file MEMMY_CONFIG points at, under tools.mcpServers:
tools:
mcpServers:
example:
type: stdio
command: npx
args:
- "-y"
- "your-mcp-server"
toolTimeout: 30
enabledTools:
- "*"type accepts stdio, sse and streamableHttp. A stdio server runs as a child process of Memmy, which means its command must exist on the same box and run as the same user. If you already keep MCP servers running on a VPS, those are the ones to list here.
Keeping the memory store private
Everything Memmy owns lives under ~/.memmy: config.yaml, the workspace, memory-service/memory.sqlite and runtime files. Scanning and ingestion happen locally, and memories are written to that local SQLite file, so the default posture is genuinely local.
Two paths do reach the network. MEMMY_CLOUD_SERVICE defaults to https://memmy-api.memtensor.cn and backs account mode with its trial tokens, so API key mode never calls it. The memory improvement program is a separate toggle in privacy settings, off until you switch it on.
A third path is easier to miss. If you configure a hosted embedding provider, the text of every memory is sent to that provider so it can be turned into a vector. Local storage does not help there. An embedding endpoint you host yourself is the only way to close it.
Keep port 18960 on the loopback address. It needs no firewall rule, because a service bound to 127.0.0.1 is not reachable from off the box at all. Reach it from your laptop over SSH instead:
ssh -N -L 18960:127.0.0.1:18960 you@your-vpsIf you ever bind it wider, set a token first. Setting storage.token in the config, or the MEMMY_MEMORY_TOKEN or MEMORY_SERVICE_TOKEN environment variable, makes every endpoint except health require a bearer token. Config values support ${ENV_NAME} references, so the token and your model API keys stay out of the file itself. That is the same habit as keeping secrets out of AI agents everywhere else, and a default deny ufw policy is your backstop if a future version changes its default bind address.
Back up ~/.memmy before you trust it
memory.sqlite is the whole store. The vectors live in that same file through the sqlite-vec extension, so one file is the backup. Copying it with cp while the service is writing can give you a torn database. Use SQLite's own backup command:
mkdir -p ~/memmy-backup
sqlite3 ~/.memmy/memory-service/memory.sqlite ".backup '$HOME/memmy-backup/memory.sqlite'"That produces a consistent copy while the service keeps running. Push it off the box on a schedule, which is what restic to off-site storage is for. Losing config.yaml costs you provider settings you can retype. Losing memory.sqlite costs you every memory, and nothing else on the machine holds a second copy.
Run the memory service under systemd
npm run memory:serve:dev in a shell dies with the shell. A unit file keeps the service up across reboots.
[Unit]
Description=Memmy memory service
After=network-online.target
[Service]
Type=simple
User=memmy
WorkingDirectory=/opt/memmy/memmy-agent
EnvironmentFile=/etc/memmy/memory.env
ExecStart=/usr/bin/npm run memory:serve:dev
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetKeep the token out of the unit. Put it in /etc/memmy/memory.env, owned by root, mode 600:
MEMMY_CONFIG=/home/memmy/.memmy/config.yaml
MEMMY_MEMORY_TOKEN=replace-this-with-a-long-random-stringsudo systemctl daemon-reload
sudo systemctl enable --now memmy-memory
systemctl status memmy-memory --no-pager
curl -sS http://127.0.0.1:18960/api/v1/healthstatus=203/EXEC in the status output means systemd could not run ExecStart at all, so check which npm: it is /usr/bin/npm on a NodeSource install and something under the user's home on nvm, which systemd will not find. A unit that starts and exits at once failed inside npm instead, and journalctl -u memmy-memory -n 50 prints the reason. The mechanics are the same as any other systemd service on a VPS.
What Memmy does not do yet
- There is no Linux desktop build. The packaging scripts cover macOS and Windows, so the workbench, its onboarding wizard and the memory dashboard are not available on the server itself.
memory:serve:devruns the TypeScript entry point throughtsx, a development path. The repository also shipsmemory:servefor compiled output. Runnpm runwith no arguments to see which scripts your checkout actually has.- Retrieval builds its search window from the latest 2,000 vector rows, then applies Top-K selection inside that window. On a very large store, an old memory can sit outside it.
- Embedding happens after capture, and a failure goes to a retry queue rather than blocking the agent's turn. A memory added a moment ago may not be findable by vector search yet.
- One SQLite file means one node. There is no clustering, so a second server is a second, separate memory.
Version 1.0.4 and roughly 329 stars as of July 2026 describe a young project. Flags, paths and script names move between releases. Read the bin field and the output of npm run in your own checkout rather than trusting a command copied from anywhere, including here.
FAQ
Why does the health check return connection refused?
Nothing is listening on port 18960. A curl exit code 7 with Failed to connect to 127.0.0.1 port 18960 means the memory service is not running or it died at startup, so read the terminal or the journal where it started. The two usual causes are a better-sqlite3 native module that does not match your Node version, fixed with npm rebuild better-sqlite3, and a Node version below 22. Confirm the socket with ss -lntp | grep 18960 once the service is up.
Where does the memmy-memory command come from after building from source?
From the bin field of the @memmy/memory workspace package, not from the repository name. Run node -p "JSON.stringify(require('./Memory/package.json').bin)" inside the checkout and it prints {"memmy-memory":"./dist/src/cli/index.js"}. That file exists only after npm run memory:build, because the build creates dist and marks the file executable. Run it as node Memory/dist/src/cli/index.js health, or symlink it into /usr/local/bin for the short name.
Can I add Memmy to Claude Code with claude mcp add?
No. Memmy is an MCP client, not an MCP server. It connects out to servers listed under tools.mcpServers in ~/.memmy/config.yaml and offers their tools to its own runtime. Claude Code reaches Memmy the other way, by running the memmy-memory CLI as a shell command, guided by an instruction file that memmy-memory init --agent writes into the agent's rules directory.
Does running Memmy send my memories to a cloud service?
Scanning and ingestion run locally, and memories are written to ~/.memmy/memory-service/memory.sqlite on your own disk. MEMMY_CLOUD_SERVICE points at https://memmy-api.memtensor.cn for account mode and trial tokens, and the memory improvement program stays off until you enable it. The path to watch is the embedding provider: a hosted embedding model receives the text of every memory it turns into a vector, so use an endpoint you run yourself if that matters.