SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

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.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 31, 2026.

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.2.401 as of 1 November 2026; Memory is independently versioned at 2.1.7.

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. Capture first stores a separate Raw Turn, then writes one L1 Trace item for each captured step, including requests, responses, tool calls and results. 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. Capture creates L1 traces, background jobs evolve the higher layers, and explicit writes can select a layer. If those distinctions feel abstract, memory is one of the later stages in a staged path for learning to build agents, and the layers make more sense once you have written a plain agent loop yourself and watched it forget everything between runs.

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 ten built-in 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, Hermes under ~/.hermes, DeepSeek Harness under ~/.dsh/sessions, WorkBuddy under ~/.workbuddy/projects, Pi under ~/.pi/agent/sessions and QwenWork under ~/.qwenworkcn/projects. The desktop workbench can add another agent by name, and asks for its history path only when it cannot find the agent itself. The server-side scanner has no custom sources, so on a headless VPS you get the built-in ones.

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 incomplete turn contributes nothing until a later sync can complete it. 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.
  • git and a build toolchain, because better-sqlite3 is a native module that may compile during install.
  • RAM for the root workspace install and its frontend build chain.
  • Free disk for node_modules and 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 --version

node --version should print v22 or higher for the documented source build. A v18 here means the NodeSource step did not take. npm's engine-strict defaults to false, so an incompatible engine declaration normally produces a warning rather than automatically failing the install.

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

npm run memory:build compiles the @memmy/memory workspace into Memory/dist. It also builds AgentSourceCore, the memory viewer and integration assets needed by that workspace. Check that the native module loaded:

node -e "const Database = require('better-sqlite3'); const db = new Database(':memory:'); db.close(); console.log('better-sqlite3 loads')"

If that line throws instead of printing, read the error to identify the load or database initialization failure. For a native module mismatch, 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.

Recent releases also attach memmy-agent-linux-cli.tar.gz and an install.sh for Linux x64 and arm64. The installer needs Node.js 22.23.4+ within 22.x, 24.22.1+ within 24.x, or 26.12.1+ within 26.x, plus a systemd user session; it rejects other major versions. It puts the memmy and memmy-memory launchers in ~/.local/bin, and starts the memory service as a user unit on 127.0.0.1:18960. This guide keeps the source build because it is the route where you can read every script before it runs.

Start the memory service and check that it answers

npm run memory:serve:dev

That 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 server accepts the same values as flags 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.yaml

From a second shell, ask the service whether it is alive:

curl -sS http://127.0.0.1:18960/api/v1/health

Health does not ask 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, the connection failed; that alone does not establish whether a listener exists. Check the address, port and local firewall, and read the terminal running the service for startup errors. 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/add writes a memory and POST /api/v1/memory/search queries.
  • GET /api/v1/memory/:id and DELETE /api/v1/memory/:id read and remove one entry.
  • POST /api/v1/sessions/open and POST /api/v1/sessions/:sessionId/close bracket an agent session.
  • POST /api/v1/turns/start and POST /api/v1/turns/:turnId/complete record one turn.
  • GET /api/v1/panel/overview, /api/v1/panel/analysis and /api/v1/panel/items feed 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 for the desktop frontend's Vite dev server and 19001 for its hot reload socket. The project's ports page still lists 19010 for hot reload, but the Vite config sets 19001. 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 health

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

The 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, and install, upgrade, service and stop for managing the service itself. 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?

Memmy does not register itself as a server inside Claude Code. It connects through files Claude Code already reads, and how far it goes depends on which memmy-memory you run. From a source checkout, memmy-memory init --agent claude writes a memmy-memory Skill into ~/.claude/skills/ and a marked memory block into ~/.claude/CLAUDE.md, and Claude Code then runs memmy-memory as an ordinary shell command when those instructions tell it to. The launcher that the Linux installer puts in ~/.local/bin goes further: the same command also registers hooks in ~/.claude/settings.json for SessionStart, UserPromptSubmit, Stop, PostCompact and SessionEnd, which inject relevant memory and save each completed turn without the model having to decide to.

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 on the source path:

## 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. Without the hooks 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, the instruction itself saved nothing; a later history scan can still import the completed turn. 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. Hooks close the gap instead of asking nicely, which is also why durable memory sits alongside budget caps and permission rules among the DeepSeek Harness plugins worth installing.

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.

The cloud connection is one path to watch. MEMMY_CLOUD_SERVICE names the cloud origin. The code sets no default for it, but .env.example, which the install steps copy to .env, sets it to https://api.memmy.cn. It backs account mode with its trial tokens, and whenever it is set Memmy's analytics code also posts usage events to it, in API key mode too: event and command names, versions and an installation ID. The CLI reports only fixed command words, never search queries or memory text, and the analytics code sends nothing when the value is unset, so on a server that only uses API keys, empty that line in .env before you start anything. The memory improvement program is a separate toggle in privacy settings.

Model calls are another path. If you configure a hosted embedding provider, the memory text being embedded is sent to that provider so it can be turned into a vector. Local storage does not help there. Use the built-in local embedding provider or an embedding endpoint you host yourself to close that path. Hosted summary, evolution and relevance-filter models also receive memory content when those jobs run, so check those model settings too.

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

The current memory service rejects non-loopback bind addresses. Setting storage.token in the config, or the MEMMY_MEMORY_TOKEN or MEMORY_SERVICE_TOKEN environment variable, protects ordinary memory API calls with a bearer token; health stays unauthenticated, and the browser viewer uses separate checks for local requests. 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 memories recorded since your last usable backup; the service also creates a local backup before schema migrations, but that is not a current off-site 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. It runs as a memmy user, from a checkout in /opt/memmy, with its config in that user's home. Stop the copy running in your shell, then move the checkout and the config across and re-point the memmy-memory link:

sudo useradd -m -s /bin/bash memmy
sudo mkdir -p /opt/memmy
sudo mv ~/memmy-agent /opt/memmy/memmy-agent
sudo chown -R memmy:memmy /opt/memmy
sudo ln -sfn /opt/memmy/memmy-agent/Memory/dist/src/cli/index.js /usr/local/bin/memmy-memory
sudo install -d -o memmy -g memmy -m 700 /home/memmy/.memmy
sudo install -o memmy -g memmy -m 600 ~/.memmy/config.yaml /home/memmy/.memmy/config.yaml
sudo install -d -m 755 /etc/memmy

The service keeps its database under /home/memmy/.memmy, so it starts with an empty store.

[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.target

Keep 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-string
sudo 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/health

status=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 needs its exit status and journal checked: code 0 is a clean exit, while other outcomes include non-zero exits, signals and timeouts. Run journalctl -u memmy-memory -n 50 for the service's output. The mechanics are the same as any other systemd service on a VPS.

The project now offers two user-level alternatives. The README's route for a source build is memmy-memory install --service-only, which downloads the pinned Memory runtime and registers a user service, and the Linux installer sets up memmy-memory.service as a systemd --user unit. Neither enables linger, so run sudo loginctl enable-linger <user> to keep that user's service manager running after logout and start it at boot.

What Memmy does not do yet

  • There is no Linux desktop build. The packaging scripts cover macOS and Windows, so the workbench and its onboarding wizard are not available on the server itself. The memory service now includes a browser viewer at http://127.0.0.1:18960/viewer/, reachable through the SSH tunnel above. Linux gets a CLI tarball and install.sh instead.
  • memory:serve:dev runs the TypeScript entry point through tsx, a development path. The repository also ships memory:serve for compiled output. Run npm run with 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.
  • A second server with its own local SQLite database has a separate memory.

Version 1.2.401 as of 1 November 2026 still describes a young project that moves fast. 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?

A curl exit code 7 with Failed to connect to 127.0.0.1 port 18960 establishes a failed connection, not whether Memmy is stopped, crashed, listening elsewhere or inaccessible. Check the address and port, run ss -lntp | grep 18960, and read the terminal or journal where the service started. If the log reports a native SQLite module mismatch, run npm rebuild better-sqlite3. Check your Node version too: the source docs require Node 22 or newer, while the Linux installer accepts only Node.js 22.23.4+ within 22.x, 24.22.1+ within 24.x, or 26.12.1+ within 26.x.

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. memmy-memory init --agent claude writes a Skill and a CLAUDE.md block that tell it when, and the launcher from the Linux installer also adds Claude Code hooks that save turns automatically.

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, set to https://api.memmy.cn in .env.example, serves account mode and trial tokens and receives usage analytics without memory text while it is set, so empty it if you only use API keys. The memory improvement program has a separate privacy toggle. A hosted embedding model receives the memory text it turns into a vector; use the built-in local provider or an endpoint you run yourself to keep that processing local. Hosted summary, evolution and relevance-filter models also receive memory content when those jobs run.

Was this article useful?