This guide covers how to spin up and test MemoryOps locally from scratch.
- Rust 1.88.0 (
rustupwill auto-install this via therust-toolchain.tomlfile) - Docker + Docker Compose
- sqlx-cli (install via:
cargo install sqlx-cli --no-default-features --features rustls,postgres) - Node.js 20+ and npm
- Ollama (for local LLM summarization — the default provider in
config.tomlisollama/llama3athttp://localhost:11434) - Optional: OpenAI or Anthropic API key if you prefer switching providers.
Start by copying the example environment variable files:
cp .env.example .envRequired vs Optional Variables:
- Core connection strings (
DATABASE_URL,REDIS_URL,QDRANT_URL), server details (APP_HOST,APP_PORT),APP_SECRET_KEY, andWORKSPACE_CREATION_SECRETare required. APP_ENV=developmentenables local-friendly defaults where supported, but webhook secrets are still stored per workspace.- Webhook signing secrets are registered per workspace through the integrations API for local and production use.
- AI Provider keys (
OPENAI_API_KEY,ANTHROPIC_API_KEY) are optional unless you configure those providers inconfig.toml.
Next, configure the frontend:
cp frontend/.env.example frontend/.env.localThe VITE_MEMORYOPS_WORKSPACE_ID is set to a placeholder. You will need to update this to match a real workspace UUID after bootstrapping (Step 5).
MemoryOps relies on PostgreSQL, Redis, and Qdrant. Start them in the background using Docker Compose:
docker compose up -d- PostgreSQL (5432): Main relational database. Health check ensures
pg_isready. - Redis (6379): Backs the processor queues and caching.
- Qdrant (6333 / 6334): Vector search backend. Exposes HTTP on 6333 and gRPC on 6334.
Note on Data Persistence:
These services use named Docker volumes (postgres-data, redis-data, qdrant-data) which persist your data across restarts.
Optional MCP Server:
If you want to run the MCP server for AI client connections (Open WebUI, Claude Code, etc.),
override the docker-compose.yml default transport (sse is deprecated — use http):
docker compose --profile mcp run --rm --service-ports \
-e MCP_TRANSPORT=http \
-e MCP_PORT=3003 \
mcpThe MCP server will be available at http://localhost:3003/mcp.
See docs/mcp-transport.md for the full transport reference.
With your infrastructure running and your DATABASE_URL correctly set in .env, run the database migrations:
source .env # Ensure DATABASE_URL is available
sqlx migrate runThis applies all 14 migrations in order, covering: init schema, ingestion indexes, processor, retrieval, workspaces, api_keys, audit_log, integrations, retrieval_traces, soft_delete, scheduler, promotion, slack, and linear_jira.
Run the API server from the root of the workspace:
cargo run -p api(Optionally, explicitly point to the config: cargo run -p api -- --config config.toml)
The server will bind to APP_HOST:APP_PORT (default 0.0.0.0:8080).
Note: The first run will compile all 6 workspace crates (api, common, ingestion, mcp, processor, retrieval), which typically takes a 2-5 minute cold build.
Verify the server is up:
curl http://localhost:8080/healthBefore you can use the frontend, bootstrap a workspace. You can use the provided Node.js helper script to automatically create a workspace and save the credentials.
# Requires WORKSPACE_CREATION_SECRET from your .env
WORKSPACE_CREATION_SECRET=<your-creation-secret> node scripts/bootstrap.mjsThis will create a uniquely named workspace and save the workspace_id and api_key to .memoryops.local.json.
Alternatively, via curl:
curl -sS -X POST http://localhost:8080/v1/workspaces \
-H 'Content-Type: application/json' \
-H 'x-admin-token: <your-creation-secret>' \
-d '{"name": "Local Dev Workspace"}'Example response:
{
"workspace_id": "0196f6c1-7e42-7f4f-8a6b-2945ea7f1e9a",
"api_key": "YOUR_MEMORYOPS_API_KEY"
}The api_key is returned in plaintext exactly once. Store it now — it cannot be
retrieved again. To create additional keys later, use
POST /v1/workspaces/{workspace_id}/keys (requires authentication).
When spinning up a local test environment or running within Docker, you must populate the workspace with demo/seed data so that retrieval and search queries have context to operate on.
Run the seeding script using the credentials returned from the bootstrap step:
API_KEY=<your-returned-api-key> WORKSPACE_ID=<your-returned-workspace-id> node scripts/seed.mjsOnce populated, follow these steps to hook up the frontend:
- Update
frontend/.env.local— setVITE_MEMORYOPS_WORKSPACE_IDto your workspace UUID. - Paste the
api_keyinto the Settings modal when you first open the frontend.
Open a new terminal, install dependencies, and start the Vite dev server:
cd frontend
npm install
npm run dev- The app is available at
http://localhost:5173. - The Vite config proxies
/v1directly tohttp://localhost:8080, preventing CORS issues. - On first load: Paste the API key into the Settings modal. The frontend does not persist this to
localStorage— it is kept only in memory via Zustand.
To run unit and integration tests:
cargo testIntegration Tests: These tests require the ephemeral test stack. Start it up:
docker compose -f docker-compose.test.yml up -d- The test stack uses separate ports (
15432,16379,16333/16334) to prevent collision with your main dev stack. - It uses
tmpfsvolumes, meaning the data is ephemeral and automatically wiped when containers stop.
Run tests using the test database connection:
DATABASE_URL=postgres://memoryops:memoryops@localhost:15432/memoryops_test cargo testFrontend Tests:
cd frontend
npm run testMemoryOps enforces strict workspace-level lints (denying unwrap_used, expect_used, dbg_macro, and todo).
Format code:
cargo fmt --allRun Clippy:
cargo clippy -p api -p common -p ingestion -p processor -p retrieval -p mcp -- -D warningsThe slow path uses an LLM for summarization. The default provider configured in config.toml is ollama using the llama3 model.
Install Ollama, then run:
ollama pull llama3
ollama serve- If Ollama is not running: Slow-path summarization jobs will fail and enter the Dead Letter Queue (DLQ).
- Retrying jobs: You can retry jobs via
POST /v1/dlq/:id/retryor using the DLQ UI at/dlq. - To skip LLM in Dev: Edit
config.toml, change[llm]provider to"openai", setOPENAI_API_KEYin.env, or simply let DLQ items accumulate.
MemoryOps divides memory into two functional layers:
- Episodic Memory: Discrete, timestamped occurrences (e.g., Pull Requests merged, Slack incidents, commits). These have importance scores assigned automatically, decay mathematically over time using half-life values (fading in query relevance to prevent context pollution), and are the raw events processed by workers.
- Semantic Memory: Durable, high-level facts, guidelines, or architectural decisions (e.g., database configs, directory guidelines, team coding standards). These are permanent, do not undergo decay, are version-controlled upon modification, and are generated manually via API or automatically by clustering similar episodic events.
The Ingestion Pipeline is the entry point for all engineering tools:
- Webhook Security: All incoming webhooks (GitHub, Slack, Jira, Linear) are HMAC signature-verified using registered workspace integration secrets.
- Atomicity & Speed: Ingestion returns
202 Acceptedimmediately. It writes theRawEventto PostgreSQL and registers theXADDstream entry to Redis atomically in a single transaction. - Async Queue Processing: Background workers consume the stream asynchronously, extracting entities (Repo, Topic, Branch, File, Person) and scoring event importance without blocking developer environments.
To test incoming webhook payloads:
- Navigate to
http://localhost:5173/ingestin the UI. - Send test webhook payloads for GitHub, Slack, Linear, or Jira.
- Webhook signature validation relies on the
dev-placeholdersecrets, which works out of the box whenAPP_ENV=development.
| Service | Port | Notes |
|---|---|---|
| API server | 8080 |
axum, REST |
| Frontend | 5173 |
Vite dev server |
| PostgreSQL | 5432 |
main dev DB |
| Redis | 6379 |
queue + cache |
| Qdrant HTTP | 6333 |
vector REST API |
| Qdrant gRPC | 6334 |
used by Rust client |
| MCP server | 3003 |
optional, --profile mcp |
| Test PostgreSQL | 15432 |
docker-compose.test.yml |
| Test Redis | 16379 |
docker-compose.test.yml |
| Test Qdrant | 16333/16334 |
docker-compose.test.yml |
- Port conflict on 5432: Run
lsof -i :5432to see what is using the port. Stop your local Postgres instance, or remap the port indocker-compose.yml. - Migrations fail: Ensure
DATABASE_URLis exported in your active shell and the Postgres container is healthy (docker compose ps). - Slow path jobs stuck in DLQ: Make sure Ollama is running and the model is pulled (
ollama pull llama3). - Frontend 404 on
/v1: The Vite proxy requires the API server to be running on8080. Check thatcargo run -p apiis successfully running. - Qdrant connection refused: Ensure the gRPC port
6334is bound and exposed indocker-compose.yml(the Rust client connects via gRPC). - Stale frontend image / ERR_EMPTY_RESPONSE: The container may be using an old nginx config listening on port 80 instead of 8080. Rebuild the frontend:
docker compose build --no-cache frontendand force recreatedocker compose up -d --force-recreate frontend.
If you experience persistent issues, you can reset the non-persistent containers:
docker compose down
docker compose build --no-cache api frontend mcp
docker compose up -d postgres redis qdrant
sqlx migrate run
docker compose up -d apiWARNING: Destructive Operation
docker compose down -vUsing -v will delete the Postgres, Redis, and Qdrant volumes. Use this only if you want to permanently delete all local workspaces, memories, and start completely fresh.