Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
Your own AI agent, running 24/7 with QwikClaw logoYour own AI agent, running 24/7 with QwikClaw

QwikClaw sets up and runs an always-on OpenClaw agent for you. One click, no config files, no server setup.

Deploy now
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 47,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Multi-tier memory system for AI assistants, integrating semantic, episodic, time-series, and spatial memory with pattern detection and MCP tools.

README.md

memory-mcp-server

Unified semantic memory + time-series intelligence layer for OpenHome abilities.

Architecture: memory tiers

Tier 1    — Semantic memory       entities, memories, relations, vectors
Tier 1.5  — Episodic memory       conversation sessions, turn-by-turn transcripts
Tier 1.75 — Working memory        short-lived task scratchpads, promote-on-close
Tier 2    — Time-series store     readings (numeric/categorical/composite), rollups, schedule
Tier 3    — Pattern engine        background task: promotes stable trends → Tier 1 memories
Tier 4    — Prospective memory    intentions: trigger_text → action_text, checked on each turn
Tier 5    — Spatial memory        last-known object locations with confidence decay

The pattern engine closes the loop: raw sensor data (Tier 2) automatically becomes searchable, natural-language memory ("Brian's temperature preference is consistently 68°F") that any ability can recall semantically (Tier 1).

Files

| File | Purpose | |-------------------|------------------------------------------------------| | server.py | MCP server (stdio transport, all 35 tools) | | api.py | FastAPI HTTP wrapper + admin UI mount | | admin.py | Admin UI router (served at /admin) | | voice_routes.py | Speaker identity API (/voices/* — enroll, merge, update voiceprints) | | graph_routes.py | Entity graph API (/graph SPA + /api/graph data endpoint) | | exporters/markdown.py | Markdown two-way sync — export and import of Obsidian-compatible .md files | | reembed.py | Utility to re-embed all memories when swapping models| | templates/admin | Jinja2 HTML templates for the admin UI | | templates/graph | vis.js entity graph SPA template | | integrations/ | Standalone tools that connect external systems to memory-mcp via HTTP |

Documentation

| Doc | Contents | |---|---| | docs/overview.md | What it is, motivation, integration patterns (OpenHome, HA, MQTT, IoT) | | docs/installation.md | Requirements, step-by-step setup, first run, verification | | docs/quickstart.md | First entity, memory, reading — common operations with curl examples | | docs/api-reference.md | Full HTTP API — every endpoint, request/response shapes, examples | | docs/admin-ui.md | Admin dashboard guide, pages, reading confidence, prune, security | | docs/ai-backend.md | AI backend config, provider examples, model swap guide | | docs/pattern-engine.md | How detectors work, all 5 detector types, how to add new ones | | docs/retention.md | Retention policy config, what gets deleted, storage estimates | | docs/deployment.md | systemd service, Docker Compose, reverse proxy, environment config | | docs/maintenance.md | Keeping it healthy — backups, upgrades, model swaps, reembed.py walkthrough | | docs/testing.md | Running tests, fixture design, what is and isn't covered | | docs/troubleshooting.md | Common errors, what they mean, how to fix them | | integrations/README.md | Integration index — MQTT bridge, HA state poller, OpenHome, Cloudflare | | integrations/background_example.py | Background worker template — health data, environment sensors, weather | | integrations/ha_state_poller.py | Pull-based HA state poller — polls HA REST API, pushes to memory-mcp | | integrations/homeassistant/README.md | HA package setup — rest_commands, automations, scripts | | integrations/openhome/README.md | OpenHome ability setup — background daemon + recall skill | | integrations/cloudflare/README.md | Cloudflare Tunnel setup — safe internet exposure for cloud callers |

Setup

# Install deps
pip install -r requirements.txt

# Pull embedding and LLM models (Ollama default)
ollama pull nomic-embed-text
ollama pull llama3.2

# Run MCP server (for OpenHome abilities)
python server.py

# Run HTTP API + admin UI (for HA webhooks, Node-RED, scripts)
python api.py          # listens on :8900
                       # admin UI at http://localhost:8900/admin/

AI Backend

Uses the OpenAI-compatible API (/v1/embeddings + /v1/chat/completions). Works with Ollama, OpenAI, LM Studio, Together AI, or any compatible provider. Configure via environment variables — no code changes needed:

# Default (local Ollama)
export MEMORY_AI_BASE_URL=http://localhost:11434/v1
export MEMORY_EMBED_MODEL=nomic-embed-text   # 768-dim
export MEMORY_LLM_MODEL=llama3.2

# OpenAI
export MEMORY_AI_BASE_URL=https://api.openai.com/v1
export MEMORY_AI_API_KEY=sk-...
export MEMORY_EMBED_MODEL=text-embedding-3-small
export MEMORY_EMBED_DIM=1536
export MEMORY_LLM_MODEL=gpt-4o-mini

Split backends are supported — embed and LLM can run on different hosts:

# nomic-embed-text on a Raspberry Pi 4, LLM on a GPU machine
export MEMORY_AI_BASE_URL=http://pi4.local:11434/v1
export MEMORY_LLM_BASE_URL=http://gpu-host.local:11434/v1

See docs/ai-backend.md for full configuration guide, provider examples, and split backend setup.

Source trust tiers

Every memory carries a trust tier that controls conflict resolution. When a new fact is written, it can only supersede an existing contradicting memory if its trust is equal or higher. Lower-trust sources cannot overwrite what you explicitly told the system.

| Tier | Label | When to use | |---|---|---| | 5 | user | Direct user statements, manual entries via admin UI | | 4 | hardware | Verified sensors, signed device data | | 3 | system | Pattern engine promotions, LLM-extracted facts | | 2 | inferred | Working memory promotions, low-confidence extractions | | 1 | external | Third-party imports, unverified webhooks |

Example: a sensor (tier 4) recording "bedroom temperature is 68°F" will not overwrite an explicit user statement (tier 5) "I keep my bedroom at 66°F at night."

Set via the source_trust parameter on remember / POST /remember. Defaults to MEMORY_TRUST_DEFAULT_REMEMBER (env var, default: 5=user).

Confidence decay

Memory confidence decays automatically over time so stale facts fade gracefully instead of accumulating indefinitely. Decay runs every hour in the pattern engine.

Formula: confidence = confidence × 2^(−days / halflife)

| Category | Default half-life | Meaning | |---|---|---| | preference, habit, routine | 90 days | Stable — takes months to fade | | insight, general | 90 days | Same default | | relationship | 90 days | Same default | | Location records | 24 hours | Unconfirmed location drops to 50% overnight |

Configure per-category overrides: ``bash export MEMORY_DECAY_HALFLIFE_DAYS=90 # global default export MEMORY_DECAY_CATEGORY_HALFLIFE='{"preference": 180, "insight": 30}' export MEMORY_LOCATION_DECAY_HALFLIFE_HOURS=24 ``

Use GET /fading (or get_fading_memories) to surface memories whose confidence has dropped below a threshold — a prompt to confirm or update them.

AI call timeout

export MEMORY_AI_TIMEOUT=30    # seconds; applies to both embed() and LLM calls

Increase if using a slow local model. LLM calls use max(MEMORY_AI_TIMEOUT, 60) to guarantee at least 60 seconds for generation.

Testing

pip install -r requirements.txt
python -m pytest                     # full suite (722 tests, no Ollama needed)
python -m pytest tests/test_tools.py # just tool tests
python -m pytest tests/test_spatial.py # just spatial/location tests

See docs/testing.md for fixture design and conventions.

OpenHome SDK config

{
  "mcpServers": {
    "memory": {
      "command": "python",
      "args": ["/path/to/memory-mcp/server.py"]
    }
  }
}

Schema

TIER 1
  entities          id, name*, type, meta(JSON), created, updated
  memories          id, entity_id, fact, category, confidence, source, created, updated,
                    last_accessed, access_count, superseded_by
  relations         id, entity_a, entity_b, rel_type, meta(JSON), created,
                    valid_from, valid_until
  memory_vectors    rowid=memories.id, embedding FLOAT[768]   ← sqlite-vec

TIER 1.5
  sessions          id, entity_id, started_at, ended_at, summary, meta
  session_turns     id, session_id, role, content, ts

TIER 1.75
  working_memory_tasks   id, name, entity_id, status, ttl_ts, created, closed_at
  working_memory_slots   id, task_id, key, value(JSON), created, updated

TIER 2
  readings          id, entity_id, metric, unit, value_type, value_num,
                    value_cat, value_json, source, ts
                    (composite readings also decomposed into {metric}.{key} child rows)
  reading_rollups   id, entity_id, metric, bucket_type, bucket_ts,
                    count, avg_num, min_num, max_num, p10_num, p90_num, mode_cat
  rollup_watermarks entity_id, metric, last_ts   ← incremental build tracking
  schedule_events   id, entity_id, title, start_ts, end_ts, recurrence, meta, created

TIER 3
  promoted_patterns id, entity_id, metric, pattern_key, memory_id, detected

TIER 5
  locations         id, entity_id, container_id, container_name, confidence,
                    last_confirmed_ts, active, source, note, created
                    (active=1 → current location; active=0 → archived sighting)

Entity types (open — add any string)

person | house | room | device

Memory categories

preference | habit | routine | relationship | insight | general

Value types for readings

| value_type | field | example | |---------------|---------------|-------------------------------------------| | numeric | value_num | temperature=71.4, heart_rate=62 | | categorical | value_cat | mood="calm", presence="home" | | composite | value_json | {"mood":"calm","confidence":0.91} |

MCP Tools

Tier 1 — Semantic memory

| Tool | Description | |----------------|-------------------------------------------------------| | remember | Store a fact about any entity (embeds + indexes it) | | recall | Semantic search — multi-factor: cosine × recency × confidence | | get_context | Relevance-filtered context snapshot (preferred for ability use) | | get_profile | Full profile: memories + relationships + readings | | relate | Create directed relationship between entities | | unrelate | Soft-delete a relationship (sets valid_until, preserves history) | | forget | Delete a memory or entire entity | | extract_and_remember | LLM-powered fact extraction from conversation text |

Tier 2 — Time-series

| Tool | Description | |----------------|-------------------------------------------------------| | record | Ingest a reading (numeric/categorical/composite) | | query_stream | Query readings: raw or hour/day/week rollups | | get_trends | Natural-language trend summary for a metric | | schedule | Add a schedule event (one-off or recurring) |

Episodic memory

| Tool | Description | |------------------|-----------------------------------------------------| | open_session | Open a conversation session for an entity | | log_turn | Append a turn (user/assistant/system) to a session | | close_session | Close a session with optional summary | | get_session | Retrieve full session transcript |

Working memory (Tier 1.75)

| Tool | Description | |------------|----------------------------------------------------------------------| | wm_open | Open a task-scoped scratchpad; optional TTL and entity association | | wm_set | Write a key/value slot into an open task | | wm_get | Read one slot by key, or all slots with task metadata | | wm_list | List tasks filtered by status (open/closed/expired/all) and entity | | wm_close | Close a task; optionally promote slots to long-term memory |

FTS keyword recall + session search

| Tool | Description | |--------------------|------------------------------------------------------------------| | recall (mode=) | Add mode="keyword" or mode="hybrid" for FTS5/BM25 recall — no embedding model needed | | search_sessions | Full-text search across episodic session turns (FTS5/BM25) |

Token-budget context assembly

| Tool | Description | |-----------------------|---------------------------------------------------------------| | get_context_budget | Greedily fills a token budget with ranked memories + readings; recall_mode="keyword" for Pi/no-Ollama |

Prospective / intention memory (Tier 4)

| Tool | Description | |----------------------|----------------------------------------------------------------| | intend | Set a condition → action intention for an entity | | check_intentions | Check if current text triggers any active intentions (FTS5) | | dismiss_intention | Deactivate an intention | | list_intentions | List active (or all) intentions for an entity |

Spatial / location memory (Tier 5)

| Tool | Description | |--------------------|-------------------------------------------------------------------| | locate | Store or update where an object was last seen | | find | Return last known location with confidence + age ("where are my keys?") | | seen_at | Confirm object is still at its location; bumps confidence | | location_history | Full trail of past sightings in reverse-chronological order |

Cross-tier

| Tool | Description | |----------------|-------------------------------------------------------| | cross_query | Semantic search across memories AND live readings |

Maintenance

| Tool | Description | |------------------------|----------------------------------------------------------------| | prune | Delete raw readings older than RETENTION_DAYS (default 30d) | | get_fading_memories | Return memories whose confidence has fallen below a threshold, most faded first |

HTTP API endpoints (api.py)

GET  /health                    liveness + row counts
GET  /entities                  list all entities
POST /remember                  store a memory
POST /recall                    semantic search (mode=vector|keyword|hybrid, recency_weight, min_confidence)
POST /get_context               relevance-filtered context snapshot
GET  /profile/{entity_name}     full profile
POST /relate                    create relationship
POST /forget                    delete memory or entity
POST /record                    ingest a reading
POST /record/bulk               ingest multiple readings at once
POST /query_stream              query time-series
POST /get_trends                trend summary
POST /schedule                  add schedule event
POST /cross_query               unified search
POST /prune                     delete readings older than RETENTION_DAYS
GET  /fading                    memories below a confidence threshold (most faded first)

POST /open_session              open a conversation session for an entity
POST /log_turn                  append a turn (user/assistant/system) to a session
POST /close_session             close a session with optional summary
GET  /get_session/{id}          retrieve full session transcript
POST /extract_and_remember      LLM-extract facts from text and store as memories

POST /wm/open                   open a working-memory task scope
POST /wm/set                    set a key/value slot in an open task
POST /wm/get                    get one slot or all slots from a task
GET  /wm/list                   list tasks (?status=open|closed|expired|all&entity_name=X)
GET  /wm/{task_id}              get all slots and metadata for a task
POST /wm/close                  close a task (promote=true bundles slots into long-term memory)

POST /locate                    store/update last-known location of an object
POST /find                      return last known location with confidence + age
POST /seen_at                   confirm object is still at a location; bumps confidence
GET  /location_history/{name}   full location trail for an object

POST /search_sessions           keyword search across session turn content (FTS5/BM25)
POST /get_context_budget        token-budget context snapshot (greedy fill, truncated flag)
POST /intend                    store a prospective intention (trigger_text → action_text)
POST /check_intentions          check if text triggers any active intentions (FTS5)
POST /dismiss_intention         deactivate an intention (soft-delete)
GET  /intentions                list intentions (?entity_name=X&active_only=true)

GET  /voices/unknown            list unenrolled provisional speaker entities
POST /voices/enroll             rename provisional entity to real person
POST /voices/merge              merge provisional entity into enrolled entity
POST /voices/update_print       update voiceprint embedding (running average)

GET  /graph                     vis.js entity relationship graph (SPA)
GET  /api/graph                 entity graph data { nodes, edges }

GET  /export/markdown           export all entities as Obsidian-compatible Markdown
GET  /export/markdown/{name}    export single entity as .md file download
POST /import/markdown           import entities from Markdown files (two-way sync)

GET  /admin/                    dashboard
GET  /admin/entities            entity list
GET  /admin/entity/{name}       entity detail
GET  /admin/readings            readings stream
POST /admin/prune               prune (HTMX-friendly HTML response)

Usage examples

Ability: build context before responding to Brian

# Pull full profile (memories + latest readings + schedule)
profile = await mem.tool_get_profile("Brian")
# → inject as <memory>...</memory> in system prompt

# Or cross-query to pull what's relevant to the current question
context = await mem.tool_cross_query("how is Brian feeling today?")

Home Assistant → record sensor readings via HTTP

# configuration.yaml — rest_command
rest_command:
  push_temperature:
    url: http://localhost:8900/record
    method: POST
    content_type: application/json
    payload: >
      {"entity_name":"{{ room }}","metric":"temperature",
       "value":{{ temp }},"unit":"F","source":"ha","entity_type":"room"}

  push_presence:
    url: http://localhost:8900/record
    method: POST
    content_type: application/json
    payload: >
      {"entity_name":"{{ person }}","metric":"presence",
       "value":"{{ state }}","source":"ha"}

  push_mood:
    url: http://localhost:8900/record
    method: POST
    content_type: application/json
    payload: >
      {"entity_name":"{{ person }}","metric":"mood",
       "value":{"mood":"{{ mood }}","confidence":{{ conf }}},"source":"avatar_ability"}

Avatar ability: store inferred mood state

# After detecting mood from conversation
await mem.tool_record(
    entity_name="Brian",
    metric="mood",
    value={"mood": "focused", "confidence": 0.87},
    source="avatar_ability",
)
# The pattern engine will promote this to a memory like:
# "Brian's mood is predominantly 'focused' (72% of days)"

Query last week of temperature with daily rollup

result = await mem.tool_query_stream(
    entity_name="living_room",
    metric="temperature",
    granularity="day",
    start_ts=time.time() - 7 * 86400,
)

Cross-entity semantic query

result = await mem.tool_cross_query("who in the house prefers a cooler environment?")
# Returns: matching memories (explicit preferences) + live temperature readings scored by relevance

Swapping embedding models

# 1. Set the new model and dimension via env vars
export MEMORY_EMBED_MODEL=mxbai-embed-large
export MEMORY_EMBED_DIM=1024

# 2. Pull the new model
ollama pull mxbai-embed-large   # 1024-dim — richer but slower

# 3. Re-embed all memories (non-destructive — only rebuilds memory_vectors)
python reembed.py --dry-run     # preview
python reembed.py               # run it

Expanding the schema

  • New entity types: pass any string — SQLite won't enforce the enum
  • New metric names: pass any string to record() — fully dynamic
  • New memory categories: same — add to enum in schema or free-text
  • New pattern detectors: write _detect_*(entity_name, metric, data) → list[tuple], call it via _maybe_promote() in _promote_patterns(). See docs/pattern-engine.md.
  • New rollup statistics: add columns to reading_rollups and compute in _build_rollups()
  • Structured entity attributes: use the meta JSON column on entities

(e.g. {"age": 35, "diet": "vegetarian", "wake_time": "06:30"})

  • Retention window: change RETENTION_DAYS in server.py. See docs/retention.md.

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Vector & Memory servers.