Skip to content

Dzarlax-AI/personal_memory

Repository files navigation

Personal Memory

A self-hosted memory layer that gives MCP-compatible AI clients durable, user-controlled context across sessions.

Personal Memory stores facts as semantic memory, retrieves the context relevant to the current conversation, and can optionally search a personal document library, expose Todoist tools, and visualize what has been remembered. The service runs on infrastructure you control and presents its capabilities through Streamable HTTP MCP endpoints.

Persistent facts are the core product. Document RAG, Todoist, OAuth onboarding, and the visualization dashboard are optional features that can be enabled independently.

Contents

Why Personal Memory

AI conversations are usually stateless. A client may know your preferences, project decisions, or working conventions during one session and lose them in the next. Repeating that context in every prompt is slow, inconsistent, and difficult to maintain.

Personal Memory provides a durable context layer between you and your AI clients:

  • save explicit facts, preferences, constraints, and decisions once;
  • retrieve them by meaning rather than exact wording;
  • organize them without coupling memory to a single client or model;
  • keep temporary information from living forever;
  • inspect, export, update, or delete what has been stored;
  • optionally search your own Markdown and text files through the same MCP endpoint.

The result is continuity without embedding a growing personal profile into every prompt or tying it to one AI provider.

What It Can Do

Capability What it provides Availability
Semantic memory Store, recall, update, export, and safely prune durable facts Core
Document search Hierarchical semantic search across Markdown and text files Optional: ENABLE_RAG=true
Todoist tools Read projects and labels; read and manage tasks without exposing the token to clients Optional: ENABLE_TODOIST=true
Visualization Browse facts by topic, timeline, similarity graph, duplicates, and document tree Optional: ENABLE_VIZ=true
OAuth onboarding Authenticate compatible clients with an external or self-hosted OIDC issuer Optional: OAUTH_ENABLED=true
Snapshots Create and retain Qdrant snapshots on a schedule Built in

Facts and documents are embedded by the TEI service inside the deployment. Optional Todoist integration calls the Todoist API, and OAuth mode depends on the issuer you configure.

How It Works

flowchart LR
    Client["MCP client"] -->|"HTTPS + API key or OAuth"| App["Personal Memory"]
    App -->|"embed text"| TEI["Text Embeddings Inference"]
    App -->|"facts and document vectors"| Qdrant["Qdrant"]
    Docs["Markdown and text files"] --> Indexer["RAG indexer"]
    Indexer --> TEI
    Indexer --> Qdrant
    App -. optional .-> Todoist["Todoist API"]
    Browser["Browser"] -. optional .-> Viz["Visualization dashboard"]
    Viz --> Qdrant
Loading

The Go server exposes two independent MCP endpoints:

  • /memory contains semantic memory and, when enabled, document-search tools;
  • /todoist exists only when Todoist integration is enabled.

TEI converts text into vectors. Qdrant stores those vectors and their metadata. The application performs retrieval, validation, deduplication, mutation safety checks, caching, backup scheduling, and graceful shutdown.

Quick Start

What the included Compose stack expects

The repository Compose file starts three services: Personal Memory, TEI, and Qdrant. It is a deployment baseline rather than a standalone local sandbox. Before starting it, provide:

  • Docker Engine and Docker Compose;
  • a DNS record for mcp.<your-domain>;
  • an existing external Docker network named traefik;
  • Traefik v3 on that network with an https entrypoint and letsEncrypt certificate resolver;
  • writable host directories for Qdrant data and snapshots.

Authentik ForwardAuth is required only when the visualization dashboard is enabled. The default memory-only deployment does not require Authentik.

1. Prepare the host

git clone https://github.com/Dzarlax-AI/personal_memory.git
cd personal_memory

sudo mkdir -p /root/memory/qdrant_storage /root/memory/qdrant_snapshots
docker network inspect traefik >/dev/null

If the final command fails, create or configure the external network before continuing. The included Compose file does not start Traefik itself.

2. Configure the service

cp .env.example .env
openssl rand -hex 32

Put the generated secret in API_KEY, set MEMORY_DOMAIN, and review the optional feature flags in .env. Keep ALLOW_INSECURE_AUTH=false on every reachable deployment.

Minimal configuration:

MEMORY_DOMAIN=example.com
API_KEY=replace_with_the_generated_secret
ALLOW_INSECURE_AUTH=false
MEMORY_USER=your_name

ENABLE_RAG=false
ENABLE_TODOIST=false
ENABLE_VIZ=false
OAUTH_ENABLED=false

3. Start and verify

docker compose up -d
docker compose ps
docker compose logs -f memory-embeddings

The first TEI start downloads the pinned embedding model and may take longer than subsequent starts. Once the services are ready:

curl -fsS https://mcp.example.com/health
# ok

curl -fsS http://127.0.0.1:6333/healthz

The first endpoint verifies public routing to the Go service. The second verifies the host-local Qdrant port.

Enabling document search

The checked-in Compose file does not mount a document directory. When enabling RAG, add a read-only bind mount to memory-mcp that matches RAG_DOCUMENTS_DIR:

services:
  memory-mcp:
    volumes:
      - /srv/personal-documents:/root/documents/personal:ro

Then set:

ENABLE_RAG=true
RAG_DOCUMENTS_DIR=/root/documents/personal
RAG_REINDEX_INTERVAL_MINUTES=30

Any filesystem synchronization mechanism can populate the host directory. The indexer only reads .md, .markdown, and .txt files.

Connect an MCP Client

Personal Memory uses the Streamable HTTP transport.

Endpoint URL Authentication
Memory and RAG https://mcp.example.com/memory X-API-Key or Bearer API key; OAuth bearer tokens when enabled
Todoist https://mcp.example.com/todoist API key only; route absent when disabled

Generic client settings

Use one of these headers with the memory endpoint:

X-API-Key: <API_KEY>

or:

Authorization: Bearer <API_KEY>

Claude Code example

Client command syntax can change; verify it against the version of your client. A current Streamable HTTP configuration looks like:

export MEMORY_API_KEY='<your API key>'

claude mcp add --transport http personal-memory \
  https://mcp.example.com/memory \
  --header "X-API-Key: $MEMORY_API_KEY" \
  --scope user

Add Todoist as a separate server only after ENABLE_TODOIST=true:

claude mcp add --transport http personal-todoist \
  https://mcp.example.com/todoist \
  --header "X-API-Key: $MEMORY_API_KEY" \
  --scope user

Clients that require a local process

Use an HTTP-to-stdio bridge such as mcp-remote. A ready-to-edit example is provided in claude_desktop_config.example.json.

OAuth-capable clients

When OAUTH_ENABLED=true, use the memory endpoint as the MCP server URL. Protected-resource metadata is available at:

https://mcp.example.com/.well-known/oauth-protected-resource

OAuth applies to /memory; the optional Todoist endpoint remains API-key-only.

Using Personal Memory

Normal usage happens through an MCP client. The exact phrasing is up to the client, but the following prompts demonstrate the intended workflows.

Remember a durable preference

Remember that I prefer PostgreSQL migrations to be reversible. Store it in the tech namespace with the tags postgres, migrations, and preference.

The client should call store_fact. Personal Memory embeds the fact, checks for near-duplicates, warns about related facts that may contradict it, and stores the metadata.

Recall context before starting work

Before changing the database layer, recall my relevant PostgreSQL and migration preferences.

The client should call recall_facts with a natural-language query. Retrieval is semantic, so the stored wording does not need to match the query.

Store a project decision

Remember that the analytics service uses ClickHouse for event storage. Put it in the projects namespace, tag it analytics, clickhouse, and architecture, with analytics as the primary tag.

Namespaces are broad context boundaries. Tags improve filtering and discovery. primary_tag selects the main grouping used by the visualization dashboard.

Search personal documents

Search my documents for the reasoning behind the authentication architecture.

With RAG enabled, the client calls search_documents. Hierarchical mode first identifies relevant folders and then searches chunks within them; flat mode searches all chunks directly.

Maintain stored context safely

Show me facts older than 180 days that would be removed, but do not delete anything.

forget_old defaults to dry_run=true and never removes facts marked permanent=true. Use exact point_id values for deterministic updates or deletions when multiple semantic matches are close.

Load operational context at session start

get_operational_context returns permanent facts plus the most frequently recalled non-permanent facts. Automation that needs plain text can call the authenticated endpoint:

GET /memory/operational?namespace=projects

Core Concepts

Facts

A fact is a short piece of durable context with an embedding and metadata. Good facts are explicit preferences, decisions, constraints, identity details, or non-obvious project context. Long-form source material belongs in the document index instead.

Namespaces

Namespaces are broad isolation and filtering boundaries such as personal, projects, work, or tech. The default namespace is default, but clients should choose meaningful stable namespaces for long-lived data.

Point IDs include both namespace and exact text, so identical text can exist safely in different namespaces.

Tags and primary tags

Tags are semantic labels. primary_tag is an optional single grouping label and must also be present in tags. When a fact has exactly one tag and no primary tag, that tag is promoted automatically.

The MCP schema accepts tags as one comma-separated string, for example tags="postgres,migrations,preference".

Lifetime

  • permanent=true prevents forget_old from deleting a fact.
  • valid_until=YYYY-MM-DD excludes an expired fact from semantic recall.
  • recall_count and last_recalled_at record how often stored context is used.

Similarity and mutations

New facts at or above the deduplication threshold are not stored twice. Related facts below that threshold may be returned as contradiction warnings.

Semantic updates and deletions require a sufficiently strong, unambiguous match. The default mutation threshold is 0.90; candidates within 0.01 of each other are treated as ambiguous. Supplying a validated point_id bypasses similarity selection while still enforcing namespace checks.

Documents

The RAG index is separate from the fact collection. Documents are split along Markdown structure, embedded in batches, and stored as versioned chunks. For a changed file, the previous complete generation remains searchable until every new chunk has been embedded and written successfully.

Tool Reference

Memory: write and maintenance

Tool Purpose
store_fact(fact, tags?, primary_tag?, namespace?, permanent?, valid_until?) Store a fact after deduplication and contradiction checks.
update_fact(new_fact, old_query?, point_id?, tags?, primary_tag?, namespace?, permanent?) Replace a fact selected by a safe semantic match or exact ID.
delete_fact(query?, point_id?, namespace?) Delete a fact selected by a safe semantic match or exact ID.
forget_old(days=90, namespace?, dry_run=true) Preview or remove old non-permanent facts.
import_facts(facts) Import a JSON-array string with at most 1,000 entries and 4 MiB.

Memory: read

Tool Purpose
recall_facts(query, namespace?, limit=5) Return semantically relevant, non-expired facts and increment recall counts.
find_related(query, namespace?, limit=5) Return related facts below the duplicate threshold.
list_facts(namespace?) List facts and metadata.
get_stats() Summarize namespaces, tags, counts, and most-recalled facts.
list_tags(namespace?) List tags and their usage counts.
export_facts(namespace?) Export facts as JSON.
get_operational_context(namespace?, top_recalled=10) Return permanent and frequently recalled context.

Document RAG

These tools are registered on /memory only when ENABLE_RAG=true.

Tool Purpose
search_documents(query, limit=5, mode="hierarchical") Search indexed chunks using hierarchical or flat mode.
reindex_documents() Start one incremental background reindex; concurrent runs are rejected.

Todoist

These tools and the /todoist route exist only when ENABLE_TODOIST=true.

Tool Purpose
get_projects() List Todoist projects.
get_labels() List personal labels.
get_tasks(project_id?, filter?, limit=20) List active tasks, optionally using Todoist filter syntax.
create_task(content, project_id?, due_string?, priority?, labels?) Create a task. Priority is an integer from 1 to 4.
update_task(task_id, content?, due_string?, priority?, labels?) Update at least one task field.
complete_task(task_id) Complete a task.
delete_task(task_id) Permanently delete a task.

Input limits

Input Limit
MCP request body 4 MiB
Fact or replacement text 64 KiB
Search or mutation query 16 KiB
Namespace 255 bytes
Tags 100 tags, 255 bytes each
Search limits and top_recalled Integer from 1 to 100
Todoist labels 100 labels, 255 characters each

Developer and Operator Guide

Deployment architecture

The included Compose file defines three services on an external traefik network:

Service Responsibility Exposure
memory-mcp Go HTTP server, MCP tools, visualization, backup scheduler Routed by Traefik
memory-embeddings Pinned TEI image and embedding model revision Docker network only
memory-qdrant Vector storage for facts and RAG collections Docker network plus 127.0.0.1:6333

The Go process serves /memory, optional /todoist, optional /viz, OAuth metadata, and public /health on one port. Qdrant and TEI do not need public internet exposure.

Local development

Requirements:

  • Go 1.24 or newer;
  • Docker for image and Compose checks;
  • curl plus sha256sum or shasum for pinned browser assets.

Canonical verification:

make test

This downloads and checksum-verifies pinned visualization assets, runs go vet ./..., runs all Go tests, and builds all three commands.

Individual commands:

make dev-deps
go test ./...
go test -race ./...
go build ./cmd/server ./cmd/indexer ./cmd/migrate-memory-ids
docker build -t personal-memory .

The final image contains:

  • /personal-memory — server entrypoint;
  • /personal-memory-indexer — one-shot RAG indexer;
  • /personal-memory-migrate-ids — memory ID migration utility.

Project layout

cmd/
  server/                 HTTP and MCP server entrypoint
  indexer/                standalone RAG indexer
  migrate-memory-ids/     namespace-aware ID migration
internal/
  backup/                 Qdrant snapshot loop
  config/                 environment loading and validation
  embeddings/             bounded TEI HTTP client
  memory/                 memory tools, cache, IDs, recall counter
  memorymigration/        dry-run/apply migration logic
  middleware/             authentication and request limits
  oauth/                  OIDC discovery and JWT verification
  qdrant/                 bounded Qdrant REST client
  rag/                    chunking, indexing, and document tools
  todoist/                Todoist REST client and MCP tools
  viz/                    embedded visualization dashboard

Configuration Reference

Configuration is read from environment variables and validated at startup. Invalid booleans, numbers, URLs, thresholds, or conditional feature settings fail fast.

Server and authentication

Variable Default Description
MCP_PORT 8000 Internal HTTP port.
MEMORY_DOMAIN Domain suffix used by Compose routing and as an OAuth resource default.
API_KEY Shared MCP secret. Required unless OAuth is enabled or insecure development mode is explicit.
ALLOW_INSECURE_AUTH false Allows startup and routes without configured credentials for isolated development. Configured API-key/OAuth checks remain active. Never enable on a reachable deployment.
MEMORY_USER claude User label stored in fact metadata.
QDRANT_URL http://memory-qdrant:6333 Qdrant base URL.
EMBED_URL http://memory-embeddings:80 TEI base URL.

Memory behavior

Variable Default Description
CACHE_TTL 60 Recall cache TTL in seconds.
DEDUP_THRESHOLD 0.97 Similarity at which a new fact is considered a duplicate.
CONTRADICTION_LOW 0.60 Lower bound for related-fact contradiction warnings.
MUTATION_MATCH_THRESHOLD 0.90 Minimum score for semantic update/delete selection.

OAuth

Variable Default Description
OAUTH_ENABLED false Allow OAuth bearer tokens on /memory.
OAUTH_ISSUER Required HTTP(S) issuer URL when OAuth is enabled.
OAUTH_RESOURCE https://mcp.<MEMORY_DOMAIN> Canonical MCP resource URL.
OAUTH_AUDIENCE OAUTH_RESOURCE Required JWT audience.
OAUTH_SCOPES memory:mcp Comma-separated required scopes.
OAUTH_JWKS_URL discovered Optional explicit JWKS URL.
OAUTH_AUTHORIZATION_SERVERS OAUTH_ISSUER Comma-separated authorization-server URLs.
OAUTH_RESOURCE_DOCUMENTATION Optional documentation URL in protected-resource metadata.

Optional capabilities

Variable Default Description
ENABLE_TODOIST false Register the Todoist client, tools, and route.
TODOIST_TOKEN Required only when Todoist is enabled. API_KEY is also required because the endpoint is API-key-only.
ENABLE_VIZ false Mount the visualization dashboard.
VIZ_PROXY_SECRET Required with visualization unless insecure development mode is explicit. Shared only by Traefik and the application.
VIZ_SIMILARITY_THRESHOLD 0.65 Default graph-edge similarity threshold.

Document RAG

Variable Default Description
ENABLE_RAG false Register document tools and indexing lifecycle.
RAG_DOCUMENTS_DIR /root/documents/personal Root directory for .md, .markdown, and .txt files.
RAG_CHUNK_MAX_BYTES 1500 Chunk target, validated from 1 byte to 1 MiB.
RAG_FOLDER_TOP_K 3 Number of folders considered by hierarchical retrieval.
RAG_FOLDER_THRESHOLD 0.50 Minimum folder score before falling back to flat search.
RAG_COLLECTION_CHUNKS doc_chunks Qdrant chunk collection.
RAG_COLLECTION_FOLDERS doc_folders Qdrant folder-summary collection.
RAG_REINDEX_INTERVAL_MINUTES 0 Automatic reindex interval; zero disables scheduling.

Backups and Compose-only embedding settings

Variable Default Description
BACKUP_INTERVAL_HOURS 24 Snapshot interval. Must be positive.
KEEP_SNAPSHOTS 7 Number of snapshots retained. Must be at least one.
EMBED_MODEL intfloat/multilingual-e5-small TEI model selected by Compose.
EMBED_MODEL_REVISION pinned commit Immutable model revision selected by Compose.

Security

Fail-closed authentication

Startup fails when neither API-key nor OAuth authentication is configured. ALLOW_INSECURE_AUTH=true is an explicit development escape hatch, not a production setting. Secret comparison uses constant-time checks.

/health is intentionally public. Memory and its operational endpoint are authenticated. Todoist is registered only when enabled. Request bodies and tool inputs are bounded before expensive embedding or database work.

Visualization trust boundary

The dashboard is intended to sit behind Authentik ForwardAuth. After authentication, Traefik overwrites X-Personal-Memory-Proxy-Secret; the application verifies the independently generated VIZ_PROXY_SECRET. Do not expose the container port directly when visualization is enabled.

Secrets

  • keep .env out of version control;
  • use independent values for API_KEY and VIZ_PROXY_SECRET;
  • do not send TODOIST_TOKEN to MCP clients;
  • expose Qdrant and TEI only on trusted networks;
  • use immutable application image tags or digests for production deployments.

Operations

Document indexing

Trigger an incremental reindex through MCP with reindex_documents, schedule it with RAG_REINDEX_INTERVAL_MINUTES, or run the standalone binary:

docker compose exec memory-mcp /personal-memory-indexer

The indexer skips unchanged files by SHA-256 hash and detects incomplete generations. Hidden directories are skipped. Stale cleanup is aborted when the filesystem walk is incomplete or would unexpectedly remove more than half the indexed files.

Backups and restore

The server creates Qdrant snapshots every BACKUP_INTERVAL_HOURS and retains the newest KEEP_SNAPSHOTS. In the included Compose topology, snapshot files are bind-mounted under:

/root/memory/qdrant_snapshots

Copy snapshots to independent storage; local retention is not an off-site backup.

Restore is an operator-controlled Qdrant action. Stop application writes first, select a verified snapshot, and follow the Qdrant recovery procedure appropriate to the deployed version. Verify collection counts and application health before re-enabling writes.

Memory point ID migration

New facts use IDs derived from namespace and exact text. Legacy IDs remain readable. The migration command is dry-run by default:

docker compose exec memory-mcp /personal-memory-migrate-ids

Review collisions and invalid before apply mode. Apply is an exclusive maintenance operation:

  1. create and verify a recent Qdrant snapshot;
  2. stop memory-mcp and every other writer to the memory collection;
  3. keep writers stopped until migration completes;
  4. run the exact deployed image on the same Docker network.
APP_IMAGE='ghcr.io/dzarlax-ai/personal-memory:sha-REPLACE_WITH_DEPLOYED_COMMIT'
docker stop memory-mcp
docker run --rm --network traefik \
  --entrypoint /personal-memory-migrate-ids \
  "$APP_IMAGE" \
  -qdrant-url http://memory-qdrant:6333 \
  -apply -confirm-writes-stopped

The command refuses unconfirmed apply mode. It compares complete normalized payloads and vectors, writes the target before deleting legacy sources, leaves conflicts untouched, and is safe to resume while writers remain stopped.

Images and deployment

The repository pins TEI, Qdrant, Docker base images, the embedding model revision, and browser-asset checksums. CI publishes application tags including immutable sha-<short> tags.

The checked-in Compose file uses the moving application tag latest as a convenient baseline. Production deployment should replace it with a tested sha-<short> tag or image digest through a separately reviewed deployment change.

After every deployment, verify:

docker compose ps
curl -fsS https://mcp.example.com/health
docker compose logs --tail=200 memory-mcp

Also verify authenticated memory access, optional routes, snapshot status, and indexing behavior relevant to the enabled features.

License and Contributions

This repository does not currently include a license or a contribution policy. Source visibility alone does not define permission to copy, modify, or redistribute the project. Add and review those files before presenting the project as licensed open source or accepting external contributions.

About

Semantic long-term memory server — stores, recalls and relates facts using Qdrant vector search. Powers personal AI assistant context across sessions

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors