Lightweight, self-hosted-first AI agent memory engine.
From "memayu hayuning bawana" (Javanese philosophy, roughly "to beautify the beauty of the world").
- Self-hosted, free forever. No license gating, no per-seat pricing. BYOK (bring your own keys) for LLM and embedding providers — you own your data end to end.
- Ships as a single static binary. Written in Rust, no runtime dependencies. Runs comfortably on a $5 VPS or a Raspberry Pi.
- Auto-detects embedding dimension mismatches. No more
expected 1536, got 768debugging sessions. Memayu probes your embedder on startup and configures itself. - Local embedding (no API key). An in-process Candle backend runs a multilingual model fully on-device with zero network calls at inference time — a pure-Rust single-binary alternative to BYOK, ideal for Raspberry Pi and offline VPS setups. The default model is
paraphrase-multilingual-MiniLM-L12-v2(384-d, covers Bahasa Indonesia + English). - ADD vs UPDATE extraction. New facts replace conflicting old facts automatically — your agent's memory stays a coherent knowledge base, not a growing append-only log.
- Raw mode. Set
MEMAYU_EXTRACTION_MODE=raw(orbehavior.extraction_mode = "raw"in the config file) to skip LLM extraction and store memories verbatim, with aggressive (0.98) deduplication — ideal for notes, logs, and low-latency ingestion. - Hybrid search. Vector similarity fused with full-text retrieval (libSQL FTS5 / Postgres tsvector) via Reciprocal Rank Fusion (RRF), so exact keyword matches and semantic matches both surface.
memayu doctor. Built-in diagnostics for self-hosted troubleshooting — validates config, storage, LLM, and embedder connectivity in one command.- Non-interactive CLI.
memayu add,memayu search,memayu list, andmemayu deletework in-process for scripting without a server.
curl -fsSL https://raw.githubusercontent.com/savioruz/memayu/main/install.sh | shOr grab a binary directly from the releases page.
cargo build --release
./target/release/memayuThis starts the Ratatui terminal UI (the default frontend). To run the web
dashboard instead, build with all features and use the serve subcommand:
cargo build --release --all-features
./target/release/memayu servedocker run --rm -p 8080:8080 \
-e MEMAYU_LLM_BASE_URL=https://api.openai.com/v1 \
-e MEMAYU_LLM_API_KEY=sk-... \
-e MEMAYU_LLM_MODEL=gpt-4o-mini \
-e MEMAYU_EMBEDDER_BASE_URL=https://api.openai.com/v1 \
-e MEMAYU_EMBEDDER_API_KEY=sk-... \
-e MEMAYU_EMBEDDER_MODEL=text-embedding-3-small \
-v $PWD/data:/data \
-e MEMAYU_LIBSQL_PATH=/data/memayu.db \
ghcr.io/savioruz/memayu:latestFor a persistent, restart-on-failure deployment with a data volume and a
readiness healthcheck, see contrib/docker-compose.yml:
docker compose up -d
docker compose logs -fThe server exposes an unauthenticated readiness endpoint at
GET /api/health:
{ "status": "setup_required" }setup_required means the process is listening but not yet usable (first-run
setup is incomplete — no admin account and/or no provider config). Once both
exist it returns { "status": "ready" }. Use this as the healthcheck target
for Docker/systemd instead of treating "port is listening" as "server is
usable".
Docker HEALTHCHECK (add to a Dockerfile or docker-compose.yml):
HEALTHCHECK --interval=5s --timeout=3s --start-period=10s --retries=5 \
CMD wget -q -O /dev/stdout http://127.0.0.1:8080/api/health | grep -q '"status":"ready"'systemd (ExecStartPost waits for readiness before the unit is active):
ExecStartPost=/bin/sh -c 'for i in $(seq 1 30); do \
curl -fsS http://127.0.0.1:18080/api/health | grep -q "\"status\":\"ready\"" && exit 0; \
sleep 1; done; exit 1'To keep memayu serve running persistently (and restart it automatically after
a reboot or crash), ship the example unit file:
sudo cp contrib/systemd/memayu.service /etc/systemd/system/memayu.serviceRead the comments in that file first — it runs as an unprivileged memayu user
and expects a config.toml in that user's config directory. Create the service
user, data directory, and a valid config.toml (e.g. from memayu setup run as
that user), then:
sudo systemctl daemon-reload
sudo systemctl enable --now memayuUseful commands:
systemctl status memayu # status plus the last few log lines
journalctl -u memayu -f # follow the service logs
systemctl restart memayu # apply changes after editing config.toml
systemctl disable memayu # stop auto-starting on bootThe service reads the same on-disk config.toml that memayu serve uses
($MEMAYU_CONFIG, else $XDG_CONFIG_HOME/memayu/config.toml, else
~/.config/memayu/config.toml) — no .env file is required. If you prefer env
overrides, add them with Environment= lines in the unit or a systemd
EnvironmentFile=.
For help diagnosing installs, see TROUBLESHOOTING.md.
memayu with no subcommand starts the default frontend — the Ratatui TUI when
compiled in, otherwise the web dashboard. In a headless or piped invocation
(no TTY), it detects that a TUI cannot render and falls back to serve mode
instead of hanging.
First-time setup is a guided wizard:
memayu setup # interactive CLI wizard (plain stdin/stdout)
memayu setup --tui # the same flow, rendered as a ratatui TUIBoth presenters ask the identical set of questions in the same order: device
check, storage backend, embedder backend, extraction mode, admin email +
password, and bind address/port. The first step probes the machine (OS, CPU
architecture, RAM, free disk) and reports whether on-device embedding is
viable. When it is, the "embedder backend" step offers local and, on choosing
it, a picker among four bundled Candle models (all-MiniLM-L6-v2,
bge-small-en-v1.5, paraphrase-multilingual-MiniLM-L12-v2, nomic-embed-text-v1.5)
with their dimensions, sizes, memory/disk footprint, CPU notes, and supported
languages. When local embedding is not viable (32-bit ARM, or too little
RAM/disk), the local option is withheld and the HTTP embedder is used
instead. On completion memayu writes config.toml, creates the admin account,
and prints a fresh mmyu_… API key exactly once. The CLI wizard reads from
plain stdin/stdout, so it also works with piped input (agent-friendly) and no
TTY. If a config file already exists, the wizard pre-fills its values as
defaults for re-configuration. In the TUI wizard, Enter/Tab/→ advance to
the next field (submitting the current step on the last field), ← moves back
to the previous field or step, ↑/↓ pick a select option, and Esc/Ctrl-C
cancels.
Other subcommands: memayu config show|check, memayu add, memayu search,
memayu list, memayu get, memayu delete, memayu reset-password,
memayu doctor, plus memayu serve (web, web feature) and memayu mcp
(mcp feature).
The web dashboard exposes an Account page (/accounts) where the logged-in
admin can change the email address and change the password (the current
password is required to confirm the change). A changed password takes effect
on the next login; the old password no longer works.
If the admin forgets their password entirely (and cannot log in to open
/accounts), reset it from the terminal:
memayu reset-password 'NewHorse-Staple-99!'This bypasses the login gate and sets a fresh password for the single admin
account, validating it against the same policy the UI enforces. It runs against
the same config.toml / MEMAYU_* env settings the server uses, so it works
for both libsql and Postgres backends. After resetting, log in with the new
password and rotate it from /accounts if you like.
All /api/memories/* routes require authentication via x-api-key header or session cookie.
# Add a memory
curl -X POST http://localhost:8080/api/memories/add \
-H 'x-api-key: YOUR_API_KEY' \
-H 'content-type: application/json' \
-d '{"content": "User lives in Jakarta", "metadata": {}}'
# Response
# {"result": {"status": "success", "memory_id": "abc123...", "dimension": 1536}}
# Search memories by semantic similarity
curl -X POST http://localhost:8080/api/memories/search \
-H 'x-api-key: YOUR_API_KEY' \
-H 'content-type: application/json' \
-d '{"query": "where does the user live", "limit": 5}'
# Response
# {"result": {"memories": [
# {"memory_id": "abc...", "content": "User lives in Jakarta", "score": 0.92, "created_at": "2026-..."}
# ]}}
# List all memories (limit defaults to 50, hard max 100)
curl 'http://localhost:8080/api/memories/list?limit=50' \
-H 'x-api-key: YOUR_API_KEY'
# Response
# {"result": {"memories": [...], "next_cursor": "abc..." | null, "total_data": 42}}
# Delete a memory
curl -X DELETE 'http://localhost:8080/api/memories/{id}' \
-H 'x-api-key: YOUR_API_KEY'
# Update a memory's content
curl -X PATCH 'http://localhost:8080/api/memories/{id}' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'content-type: application/json' \
-d '{"content": "User moved to Bandung"}'Memayu ships an MCP stdio server as memayu mcp. It auto-detects local vs. cloud mode based on environment.
Self-hosted (in-process):
{
"mcpServers": {
"memayu": {
"command": "memayu",
"args": ["mcp"],
"env": {
"MEMAYU_STORAGE_BACKEND": "libsql",
"MEMAYU_LIBSQL_PATH": "./memayu.db",
"MEMAYU_LLM_BASE_URL": "https://api.openai.com/v1",
"MEMAYU_LLM_API_KEY": "sk-...",
"MEMAYU_LLM_MODEL": "gpt-4o-mini",
"MEMAYU_EMBEDDER_BASE_URL": "https://api.openai.com/v1",
"MEMAYU_EMBEDDER_API_KEY": "sk-...",
"MEMAYU_EMBEDDER_MODEL": "text-embedding-3-small"
}
}
}
}Cloud (remote API):
{
"mcpServers": {
"memayu": {
"command": "memayu",
"args": ["mcp"],
"env": {
"MEMAYU_API_URL": "https://your-memayu-instance.example.com",
"MEMAYU_API_KEY": "mk_..."
}
}
}
}| Memayu | mem0 | Zep | Letta | agentmemory | |
|---|---|---|---|---|---|
| Language | Rust | Python | Go / Python | Python | Python |
| Binary size | ~15 MB static | N/A (Python runtime) | N/A | N/A | N/A |
| Self-hosted | Yes (single binary) | Yes (Docker / pip) | Yes (Docker) | Yes (Docker / pip) | Yes (pip) |
| Free forever | Yes, MIT | Yes (Apache 2.0) | Limited (Community tier) | Yes (Apache 2.0) | Yes (MIT) |
| Embedding dim auto-detect | Yes | No | No | No | No |
| ADD vs UPDATE extraction | Yes | N/A | Configurable | Via archival memory | No |
| Postgres | Yes (pgvector) |
Yes | Required | Required | No |
| Embedded SQL | Yes (libsql) |
SQLite (via Python) | No | No | No |
| MCP stdio | Yes | Community adapter | No | Planned | No |
| BYOK | Yes | Yes | Yes | OpenAI-only default | Yes |
Benchmark pending — RAM usage, latency, and throughput comparisons have not been measured yet. This table reflects feature parity, not performance claims.
Memayu follows a ports-and-adapters architecture with a strict dependency rule: memayu-core defines the domain logic and traits; nothing in core depends on any concrete implementation. Storage (libsql, postgres+pgvector), LLM clients, and transport layers (HTTP API, MCP stdio) are separate crates wired together in the binary.
Configuration loads from an optional TOML file at ~/.config/memayu/config.toml (or $XDG_CONFIG_HOME/memayu/config.toml), overridable via the MEMAYU_CONFIG env var. Environment variables (prefixed MEMAYU_) remain supported and override the file. See .env.example for the complete reference with defaults and descriptions.
The embedder can run fully on-device instead of calling a remote API. Set the embedder backend to local in the config file:
[embedder]
backend = "local" # "local" (on-device Candle) or "remote" (BYOK, default)
model = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"or via environment variables: MEMAYU_EMBEDDER_BACKEND=local and MEMAYU_EMBEDDER_MODEL=<HF model id>.
- No API key. A local backend needs no
base_urlorapi_key; nothing is sent over the network at inference time. - One-time download. The model weights are downloaded from Hugging Face on first use and cached under the local data directory (override with
MEMAYU_MODEL_DIR). Subsequent runs are fully offline. - Default model.
paraphrase-multilingual-MiniLM-L12-v2(384-d) is multilingual, so it handles a Bahasa Indonesia + English technical mix out of the box — measured recall@3 of 5/5 (100%) on a fixed mixed corpus in the local e2e suite.all-MiniLM-L6-v2or another HF sentence-transformer id can be substituted for English-only users. - Dimension auto-detect. The local model's output dimension is probed the same way as remote providers, so no manual dimension config is needed.
- Wizard & dashboard.
memayu setupofferslocalas the default embedder backend, and the web dashboard (/providers) exposes a backend selector.memayu doctorreports the active backend and skips HTTP probes when the backend is local. - Build. The Candle backend is compiled in by default (
memayu-llm-client'slocal-embeddingfeature). Use--no-default-featureson that crate for a smaller HTTP-only build.
Issues and pull requests are welcome. Before submitting structural changes, read CONTRIBUTING.md for guidelines.
MIT — see LICENSE.