Technical Overview
This page keeps the technical detail that used to live in the README. The README is intentionally short; use this page when you need implementation, operations, or integration details.
Installation
Clone the repository and install the development environment with uv:
git clone https://github.com/Artemon-line/ai-memory-hub.git
cd ai-memory-hub
uv sync --dev
The project requires Python 3.14 or newer.
Optional extras:
uv sync --dev --extra postgres
uv sync --dev --extra tokenizer
uv sync --dev --all-extras
Use provider extras when selecting optional storage SDKs such as Qdrant,
Milvus, Weaviate, MongoDB, Elasticsearch, OpenSearch, Redis, Vespa,
Typesense, Pinecone, Turbopuffer, Postgres, or PGVector. Use the tokenizer extra for exact
OpenAI-compatible token counting through tiktoken. Use --all-extras for
container builds or provider compatibility checks.
Provider config blocks do not install dependencies. Dependencies are installed
only by the uv sync extras you choose, or by container images that explicitly
install extras. The checked-in root Containerfile is the quickstart image and
installs only the default SQLite/LanceDB runtime dependencies. The free local
Compose examples for PostgreSQL/PGVector, MongoDB, Redis, and SQLite/LanceDB
use provider-local Containerfiles so they install only the extras needed by
that example.
ChromaDB is temporarily unavailable in v0.1.0 because the upstream
chromadb package has an unresolved critical advisory with no patched release.
The adapter code remains in the repository for future re-enable after upstream
publishes a safe version.
How It Works
Captured Conversation JSON
-> Schema Validation
-> Chunk Messages or Token Windows
-> Embed Chunks
-> Store Metadata + Vectors
-> Search / Retrieve / Ask
The hub expects structured conversation JSON. Capture/import adapters convert supported external formats into that shared schema before using the same hub ingestion path. Manual speaker-labelled transcripts are supported; export-specific capture/import adapters are tracked in the roadmap. Browser extension capture is supported as a separate extension-repo workflow that posts normalized web chat payloads to the existing insert API; see the browser extension capture plan.
API Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/memory/insert |
Validate and store a conversation |
POST |
/memory/search |
Return ranked semantic matches |
POST |
/memory/retrieve |
Retrieve a stored conversation by ID |
POST |
/memory/ask |
Build an answer from retrieved memory or facts |
POST |
/memory/facts/search |
Search normalized facts |
POST |
/memory/profile/get |
Return profile facts and a compact fact-based summary for a subject |
POST |
/memory/facts/supersede |
Mark a fact as superseded |
GET |
/health |
Liveness endpoint with redacted runtime health |
GET |
/ready |
Readiness endpoint for container orchestration |
Insert Payload
If backend-generated IDs are enabled, omit id and let the server assign the
canonical UUID. When the backend requires caller-supplied IDs, include an id:
{
"id": "11111111-2222-4333-8444-555555555555",
"source": "codex",
"timestamp": "2026-05-17T00:00:00Z",
"messages": [
{"role": "user", "text": "remember this"},
{"role": "assistant", "text": "stored"}
],
"metadata": {
"imported_at": "2026-05-17T00:00:00Z",
"summary": "User asked the assistant to remember a preference.",
"tags": ["preferences"]
}
}
Messages may use text or content; content is normalized to text.
metadata.summary is optional. Use it as a short factual retrieval hint only;
raw messages and normalized facts remain the source of truth for answers and
citations. The server also writes deterministic generated summaries for each
conversation, its topics, and its project. Generated summaries are persisted
separately from raw messages and facts; conversation summaries are exposed back
under metadata.generated_summary for search, retrieve, and CLI display.
The server also writes deterministic metadata.auto_tags and
metadata.tag_sources from source, topics, entities, and fact predicates.
Manual metadata.tags remain authoritative and are not overwritten.
For continuing source threads, clients may send metadata.upstream_thread_id
or metadata.thread_id. The server preserves upstream IDs, derives
metadata.thread_id when needed, and accepts optional
metadata.parent_conversation_id plus metadata.related_conversation_ids as
conversation UUID references.
metadata.save_intent declares why a client is saving a conversation. Accepted
values are explicit_user_request, user_confirmed, and client_auto_save.
When memory.insert_policy: require_save_intent is configured, API and MCP
inserts without an accepted marker are rejected before storage, vector indexing,
or fact extraction. When memory.insert_policy: review_pending is configured,
unmarked inserts are stored with metadata.memory_status: pending_review and
excluded from default retrieve, search, ask, fact, and profile reads until
approved through /memory/pending/approve or the memory_pending_approve MCP
tool. /memory/pending/reject and memory_pending_reject mark pending inserts
as rejected.
Conversation read operations accept memory_status to intentionally inspect
review states. The default is active. Use pending_review, rejected, or
all with /memory/search, /memory/retrieve, /memory/ask,
memory_search, memory_retrieve, or memory_ask when reviewing held or
rejected inserts.
Store one complete conversation per insert. Do not split one thread into multiple batch items. If an importer has many independent source conversations, it should call the existing single insert path once per source conversation while preserving each original thread/session boundary.
Search Result Shape
/memory/search returns ranked chunk matches in results. Each result includes:
id: canonical conversation IDscore: vector distance, where lower is betterchunk_index,role, andtext: the matched chunkconversation: the stored conversation payload, includingmetadata.generated_summarywhen a server-generated conversation summary is available, andmetadata.auto_tagswhen deterministic tags were generatedconversation_score: best score for that conversation among retrieved chunksconversation_match_count: number of retrieved chunks from that conversation
Search applies conservative conversation grouping before trimming to top_k, so
closely matched chunks from the same conversation can surface together without
hiding strong unrelated matches.
Use result_mode=threads to group matching conversations by metadata.thread_id.
Ask Result Shape
/memory/ask returns:
answer: human-readable answer text. For fact-backed answers this uses normalized display values while preserving raw source text in stored memory and fact evidence.results: chunk-shaped retrieval hits. Fact-only answers can legitimately return an emptyresultslist because the answer came from normalized facts, not retrieved chunks.citations: compact provenance for chunks or facts used in the answer.provenance: grouped source-conversation provenance.confidenceandconfidence_reason: confidence label plus the reason, such as a direct user statement, assistant statement, inferred recurring topic, or conflict.answer_basis: one ofdirect_memory,fact_layer,mixed,conflict, ornot_found.evidence: stable evidence entries used by the answer. Chunk-backed answers returntype: "chunk"entries; fact-backed answers returntype: "fact"entries.structured_evidence: split evidence for clients that do not want to inspect mixed lists.structured_evidence.factscontains normalized fact evidence andstructured_evidence.resultscontains chunk-shaped retrieval hits.facts: active normalized facts used by fact-backed answers, includingsource_quality,confidence_reason,created_at,updated_at,last_confirmed_at,superseded_at,object_raw,object_normalized, and save-intent provenance when the source memory included it. Facts derived frommetadata.save_intent: client_auto_saveuse lower confidence than explicit user-requested saves.
Pass max_context_tokens to /memory/ask to build the answer from only the
chunks that fit the requested context budget. When budgeting is active, the
response can include context_tokens_used, chunks_selected, chunks_dropped,
and tokenizer_used.
CLI
Use python -m memory.cli during development, or the packaged aim console
script after installation.
python -m memory.cli ingest conversation.json --json
python -m memory.cli reindex --json
python -m memory.cli import manual copilot-chat.txt --source vscode-copilot --json
python -m memory.cli search "local-first tools" --top-k 5 --json
python -m memory.cli retrieve <MEMORY_ID> --json
python -m memory.cli ask "What did I store about local-first tools?" --top-k 5 --json
python -m memory.cli serve --host 127.0.0.1 --port 8000
Manual imports accept multiline messages labelled with common speaker names such
as User:, You:, Human:, Assistant:, Copilot:, Claude:, or Gemini:.
Use - as the file name to read the transcript from stdin.
Shared options include --config <path>, --json, --quiet, and --verbose.
search also supports --source, --date-from, --date-to, repeated --tags,
--thread-id, and --result-mode chunks|compact|conversations|threads.
Diagnostics:
python -m memory.cli tokenizer-check --json
python -m memory.cli health --json
python -m memory.cli config-show --json
python -m memory.cli storage-check --json
Authenticated clients can inspect visible project workspaces through GET /memory/projects, GET /memory/projects/default, GET /memory/projects/{project_id}, and the matching MCP project helper tools.
Local bearer-token and project administration is CLI-first. Token creation prints the raw bearer token once; token list and revoke commands only expose stable token ids and non-secret prefixes.
python -m memory.cli admin user create jane --display-name "Jane" --json
python -m memory.cli admin user list --json
python -m memory.cli admin token create --user jane --display-name laptop --json
python -m memory.cli admin token list --user jane --json
python -m memory.cli admin token revoke <TOKEN_ID_OR_PREFIX> --json
python -m memory.cli admin project create shared-321 --owner jane --name "Shared 321" --json
python -m memory.cli admin project list --user jane --json
python -m memory.cli admin project member add shared-321 --user carl --role writer --json
python -m memory.cli admin project member list shared-321 --json
Fact review helpers:
python -m memory.cli fact-search --subject user --json
python -m memory.cli fact-search --source codex --status superseded --json
python -m memory.cli profile-get --subject user --predicate owns_guitar --source-quality corrected_by_user --save-intent-source codex --json
python -m memory.cli fact-supersede <OLD_FACT_ID> <NEW_FACT_ID> --json
MCP Interface
When interfaces.mcp is enabled, the streamable HTTP MCP endpoint is mounted at:
http://127.0.0.1:8000/mcp/
For user-facing MCP setup, run with api.auth: oauth_resource_server and open
/connect. The Connect UI shows the configured MCP resource URL, enabled
passport providers, sign-in status, hub-issued token workflow, and client setup
snippets. Google is the current live provider; meta and x are disabled
provider slots until their provider-specific flows are implemented. Snippets
remain marked Unverified until checked against current client releases. See
the Connect UI and OAuth setup guide for packages, Docker
setup, provider status, and client verification notes.
Core tools:
memory_validate(conversation_json)memory_insert(conversation_json)memory_search(query, top_k=5, limit, cursor, source, date_from, date_to, tags, thread_id)memory_retrieve(id)memory_ask(question, top_k=5, max_context_tokens=None, result_mode="chunks", source, date_from, date_to, tags, thread_id, project_id)memory_fact_search(subject=None, predicate=None, include_superseded=False, source, date_from, date_to, confidence, status, source_quality, save_intent, save_intent_source, freshness_from, freshness_to, project_id)memory_profile_get(subject="user", predicate, source, date_from, date_to, confidence, status, source_quality, save_intent, save_intent_source, freshness_from, freshness_to, project_id)memory_fact_supersede(fact_id, superseded_by)memory_project_list()memory_project_default_get()memory_project_get(project_id)
memory_profile_get returns facts plus a summary object. The summary is
generated from active normalized facts and includes freshness, source-quality
counts, filters, save-intent filters, and compact fact provenance. Insert also generates
conversation, topic, and project summaries from stored message text. Generated
summaries are stored separately from raw chunks and normalized facts; the
conversation summary is returned as metadata.generated_summary on search and
retrieve responses.
memory_insert accepts one complete conversation object. There is intentionally
no bulk MCP insert tool. Clients may include a short metadata.summary, but
must still send the complete messages list.
Resources:
memory://conversation/example
Templates:
memory://conversation/{id}memory://search/{query}memory://timeline/{day}memory://health
Prompts:
save_conversationsearch_memoryask_memorysummarize_conversation
MCP Session Flow
Initialize:
curl -i -X POST http://127.0.0.1:8000/mcp/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "example-client", "version": "0.1.0"}
}
}'
Use the returned Mcp-Session-Id for later MCP calls:
curl -X POST http://127.0.0.1:8000/mcp/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: <SESSION_ID>" \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}'
Native Clients
Register ai-memory-hub as an MCP server in clients such as Codex, opencode,
Claude, Copilot, VS Code, or Cursor, then point the client at:
http://127.0.0.1:8000/mcp/
The same memory operations remain available through HTTP API endpoints, so agent clients and direct service clients can share one backend.
Storage Backends
The runtime uses one metadata provider and one vector provider. Select them with
providers.metadata_db and providers.vector_db. The blocks under
storage.metadata_providers and storage.vector_providers only hold settings
for possible providers; they are not active unless selected.
Multilingual Retrieval
ai-memory-hub is not English-only. It stores Unicode text and uses the configured embedding model for semantic retrieval. Multilingual retrieval works when the configured embedding model supports the languages in the stored conversation and the user's query.
The embedding provider, model, dimension, and relevant options form one vector space. Use the same embedding configuration for ingestion and query-time retrieval. If a persistent vector index was built with a different embedding model or provider, reindex it or use a separate vector namespace/index. Dimension checks catch many unsafe swaps, but same-dimension model changes can still corrupt ranking if mixed silently.
Changing Embedding Models For Existing Data
Use this runbook when you already have a metadata database containing your conversations and you want to switch to another embedding model.
Do not point a new embedding model at the old vector table, collection, index, or namespace. The vectors already stored there were produced in the old model's embedding space. Mixing them with new vectors corrupts ranking, and same-dimension models are not interchangeable.
- Stop
aim serveand any API, MCP, or CLI clients that can insert memory. - Back up metadata and vectors.
- Default local metadata: back up
data/metadata.sqlite3. - Default local vectors: back up
data/lancedb. - PGVector: back up the configured
storage.vector_providers.pgvector.table_name. - Hosted vector stores: take the provider's normal snapshot/export before changing collections, indexes, namespaces, or tables.
- Choose the new embedding settings and set all of them together:
providers:
embeddings: http
embedding_model: nomic-embed-text
embedding_dimension: 768
embeddings:
endpoint: http://127.0.0.1:11434/v1
- Point the new config at an empty vector destination.
- SQLite + LanceDB: keep
paths.data_dirunchanged if you want the samedata/metadata.sqlite3, but back up and replace only thedata/lancedbvector directory. Use a newpaths.data_dironly when you want a separate metadata database too. - PGVector: use a new
storage.vector_providers.pgvector.table_name, such asmemory_vectors_nomic_768. - Qdrant, Milvus, Weaviate, Redis, Typesense, MongoDB Atlas, Elasticsearch, OpenSearch, Pinecone, Turbopuffer, and Vespa: use a new collection, index, namespace, table, or schema name that contains no old-model vectors.
- Keep the same metadata database unless you intentionally want a completely separate memory store. Keeping metadata preserves conversations, facts, generated summaries, projects, users, and tokens while the vector store is rebuilt.
- Recalculate embeddings from the stored metadata:
uv run aim reindex --config new-embedding-config.yaml --json
aim reindex reads the existing conversation payloads from metadata, rebuilds
chunks, embeds them with the active embedding config, and writes vectors to the
active vector store with replacement semantics. It does not require original
transcript files or hand-written JSON files.
Useful options:
--project-id <id>reindexes one project workspace.--limit <n>reindexes only the firstnstored conversations, useful for a smoke test before a full run.-
--include-inactivealso recalculates vectors forpending_revieworrejectedconversations. The default only reindexes active memory. -
Verify the new index before removing the old one:
uv run aim storage-check --config new-embedding-config.yaml --json
uv run aim search "project memory smoke" --config new-embedding-config.yaml --top-k 5 --json
uv run aim ask "What do you remember about this project?" --config new-embedding-config.yaml --top-k 5 --json
Rollback is simple if you kept the backups: restore the old config that points at the old embedding model and old vector destination. The metadata database can stay in place when the migration only replayed duplicate conversation payloads.
providers:
metadata_db: postgres
vector_db: pgvector
storage:
metadata_providers:
postgres:
url: postgresql://user:password@127.0.0.1:5432/memory
vector_providers:
pgvector:
url: postgresql://user:password@127.0.0.1:5432/memory
table_name: memory_vectors
SQLite + LanceDB
The default local setup stores metadata in SQLite and vectors in LanceDB:
providers:
metadata_db: sqlite
vector_db: lancedb
paths:
data_dir: ./data
SQLite + Qdrant
Use Qdrant as a local Docker or Qdrant Cloud vector backend:
providers:
metadata_db: sqlite
vector_db: qdrant
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
qdrant:
url: http://127.0.0.1:6333
api_key: ""
collection: memory_vectors
Install the optional dependency with uv sync --extra qdrant.
SQLite + Milvus
Use Milvus or Zilliz for larger vector deployments:
providers:
metadata_db: sqlite
vector_db: milvus
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
milvus:
uri: http://127.0.0.1:19530
token: ""
collection: memory_vectors
Install the optional dependency with uv sync --extra milvus.
SQLite + Weaviate
Use Weaviate for schema-rich vector deployments:
providers:
metadata_db: sqlite
vector_db: weaviate
storage:
vector:
allow_fallback: false
vector_providers:
weaviate:
url: http://127.0.0.1:8080
api_key: ""
collection: MemoryVector
Install the optional dependency with uv sync --extra weaviate.
SQLite + Elasticsearch
Use Elasticsearch when vector storage should live in an existing Elastic cluster:
providers:
metadata_db: sqlite
vector_db: elasticsearch
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
elasticsearch:
url: http://127.0.0.1:9200
username: ""
password: ""
index: memory_vectors
Install the optional dependency with uv sync --extra elasticsearch.
SQLite + OpenSearch
Use OpenSearch when vector storage should live in an existing OpenSearch cluster:
providers:
metadata_db: sqlite
vector_db: opensearch
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
opensearch:
url: http://127.0.0.1:9200
username: ""
password: ""
index: memory_vectors
Install the optional dependency with uv sync --extra opensearch.
SQLite + Redis/RediSearch
Use Redis when Redis Stack already owns operational infrastructure and RediSearch should host vectors:
providers:
metadata_db: sqlite
vector_db: redis
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
redis:
url: redis://127.0.0.1:6379/0
index: memory_vectors
key_prefix: "memory_vectors:"
Install the optional dependency with uv sync --extra redis.
SQLite + Vespa
Use Vespa when a deployed Vespa application already owns large-scale retrieval infrastructure and ai-memory-hub should feed hub-owned embeddings into it:
providers:
metadata_db: sqlite
vector_db: vespa
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
vespa:
url: http://127.0.0.1:8080
token: ""
namespace: memory
schema: memory_vector
rank_profile: vector_similarity
Install the optional dependency with uv sync --extra vespa. Deploy the Vespa
application package and schema before starting ai-memory-hub.
SQLite + Typesense
Use Typesense when vector storage should live in a lightweight search engine with first-class filtering and faceting:
providers:
metadata_db: sqlite
vector_db: typesense
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
typesense:
url: http://127.0.0.1:8108
api_key: ""
collection: memory_vectors
Install the optional dependency with uv sync --extra typesense.
SQLite + Pinecone
Use Pinecone when vector storage should live in hosted managed/serverless infrastructure:
providers:
metadata_db: sqlite
vector_db: pinecone
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
pinecone:
api_key: ""
index: memory-vectors
namespace: default
cloud: aws
region: us-east-1
create_index: false
Install the optional dependency with uv sync --extra pinecone.
SQLite + Turbopuffer
Use Turbopuffer when vector storage should live in hosted object-storage-backed search infrastructure:
providers:
metadata_db: sqlite
vector_db: turbopuffer
storage:
vector:
allow_fallback: false
distance: cosine
vector_providers:
turbopuffer:
api_key: ""
namespace: memory-vectors
region: gcp-us-central1
Install the optional dependency with uv sync --extra turbopuffer.
MongoDB Metadata And Atlas Vectors
Use MongoDB for metadata when Mongo already owns application persistence, and use MongoDB Atlas Vector Search when Atlas should also own vectors:
providers:
metadata_db: mongodb
vector_db: mongodb_atlas
storage:
metadata_providers:
mongodb:
uri: mongodb://127.0.0.1:27017
database: ai_memory_hub
conversations_collection: conversations
vector_providers:
mongodb_atlas:
uri: mongodb+srv://example.mongodb.net/app
database: ai_memory_hub
collection: memory_vectors
index: memory_vector_index
Install the optional dependency with uv sync --extra mongodb.
Postgres Metadata
Use Postgres for conversation metadata:
providers:
metadata_db: postgres
vector_db: lancedb
storage:
metadata_providers:
postgres:
url: postgresql://user:password@127.0.0.1:5432/memory
PGVector
Use PGVector for vector storage:
providers:
metadata_db: postgres
vector_db: pgvector
storage:
metadata_providers:
postgres:
url: postgresql://user:password@127.0.0.1:5432/memory
vector_providers:
pgvector:
url: postgresql://user:password@127.0.0.1:5432/memory
table_name: memory_vectors
In-Memory Vectors
Use the in-memory vector backend for tests and short-lived local experiments:
providers:
vector_db: memory
Configuration
Configuration is loaded from config.yaml by default. A fuller example is
available in example.config.yaml.
providers:
embeddings: local
embedding_model: local-hash
embedding_dimension: 32
metadata_db: sqlite
vector_db: lancedb
storage:
dry_run: false
allow_trusted_appends: false
# Metadata schema compatibility allow-list. Version 1 is current.
metadata_schema_versions: [1]
vector:
allow_fallback: true
distance: cosine
vector_providers:
qdrant:
url: http://127.0.0.1:6333
api_key: ""
collection: memory_vectors
milvus:
uri: http://127.0.0.1:19530
token: ""
collection: memory_vectors
weaviate:
url: http://127.0.0.1:8080
api_key: ""
collection: MemoryVector
mongodb_atlas:
uri: ""
database: ai_memory_hub
collection: memory_vectors
index: memory_vector_index
elasticsearch:
url: http://127.0.0.1:9200
username: ""
password: ""
index: memory_vectors
opensearch:
url: http://127.0.0.1:9200
username: ""
password: ""
index: memory_vectors
redis:
url: redis://127.0.0.1:6379/0
index: memory_vectors
key_prefix: "memory_vectors:"
pinecone:
api_key: ""
index: memory-vectors
namespace: default
cloud: aws
region: us-east-1
create_index: false
turbopuffer:
api_key: ""
namespace: memory-vectors
region: gcp-us-central1
vespa:
url: http://127.0.0.1:8080
token: ""
namespace: memory
schema: memory
rank_profile: vector_similarity
typesense:
url: http://127.0.0.1:8108
api_key: ""
collection: memory_vectors
metadata_providers:
mongodb:
uri: ""
database: ai_memory_hub
conversations_collection: conversations
tokenizer:
enabled: false
encoding: cl100k_base
ask:
max_context_tokens: 2000
retrieval:
vector_score_threshold: 7.5
keyword_enabled: true
keyword_candidate_limit: 50
keyword_weight: 0.25
metadata_weight: 0.15
candidate_multiplier: 3
memory:
insert_policy: permissive
chunking:
strategy: message
max_tokens: 800
overlap_tokens: 80
schema:
file: ./memory/schema/conversation.schema.json
interfaces:
mcp: true
api: true
paths:
data_dir: ./data
logs_dir: ./logs
embedding_endpoint:
base_url: http://localhost:11434/v1
api_key: dummy_key
metadata_schema_versions means "this app build supports these metadata store
schema versions." It is an array so future rolling migrations can temporarily
support old and new database layouts, for example [1, 2]. Version 1 is the
first and current metadata schema version. Startup fails if the active metadata
store reports a version outside this list.
The default chunking.strategy: message keeps one chunk per normalized message.
Set chunking.strategy: token to split long messages into token windows with
chunking.max_tokens and chunking.overlap_tokens. Token chunking is opt-in and
uses tiktoken when available, with a deterministic local heuristic fallback.
Retrieval first gathers vector candidates, filters low-confidence vector matches
with retrieval.vector_score_threshold, then applies deterministic keyword and
metadata reranking. retrieval.keyword_enabled also allows exact keyword matches
from metadata storage to supplement vector candidates when the active metadata
store supports text lookup.
tokenizer.encoding is an encoding name such as cl100k_base, not a model file
path. ai-memory-hub does not download tokenizer files itself. When the optional
tiktoken extra is installed, tiktoken resolves and caches the encoding data.
For persistent or offline deployments, set TIKTOKEN_CACHE_DIR to a writable
directory and prewarm the cache during setup:
TIKTOKEN_CACHE_DIR=./data/tiktoken-cache uv run python -c "import tiktoken; tiktoken.get_encoding('cl100k_base')"
Check which tokenizer path will be used:
uv run python -m memory.cli tokenizer-check --json
Containers
Build the local image:
docker build -t ai-memory-hub:local -f Containerfile .
Run the default image locally:
docker run --rm -p 127.0.0.1:8000:8000 ai-memory-hub:local
The image exposes the API and MCP service on port 8000 and starts with:
/app/.venv/bin/aim serve --host 0.0.0.0 --port 8000
The built-in container configuration uses deterministic local embeddings, SQLite
metadata, and LanceDB vectors so the image starts without external model or
database services while keeping local vector state persistent under /app/data.
Container images run as a non-root user by default and are built so OpenShift can
override the runtime UID while keeping root-group write access to /app/data,
/app/logs, and /app/.uv-cache. Use /ready for readiness probes and
/health for liveness probes.
Kubernetes probe example:
readinessProbe:
httpGet:
path: /ready
port: 8000
livenessProbe:
httpGet:
path: /health
port: 8000
For persistent local container data, mount /app/data and optionally /app/logs.
For custom configuration, mount a config file and start with:
uv run aim serve --config <path>
Keep secrets in a mounted config file or injected environment variables, not in committed images or repository files.
For a reusable Docker/Podman Compose setup that runs ai-memory-hub with Postgres metadata and PGVector vectors:
cd examples/local-stack
docker compose up --build
The example binds ai-memory-hub on host port 8000 for LAN clients. Use
http://<HOST_LAN_IP>:8000/mcp/ from another PC on the same network. It also
enables tokenizer budgeting with the optional tiktoken extra. For remote
Ollama embeddings, see examples/local-stack/config.oauth-ngrok.yaml.
Additional checked-in provider examples are under examples/storage_providers.
They cover SQLite/LanceDB, in-memory vectors, Qdrant, MongoDB metadata,
MongoDB Atlas Vector Search, Milvus, Weaviate, Elasticsearch, OpenSearch,
Redis/RediSearch, Vespa, Typesense, Pinecone, and Turbopuffer.
Testing
Run the full local suite:
uv run pytest
Run storage tests only:
uv run pytest tests/integration/test_storage_features.py
Default local runs do not require external storage services. Live provider tests
are skipped unless their AMH_TEST_* environment variables are set. GitHub
Actions runs fake-client provider contracts in the normal CI suite and service-
backed live provider checks in .github/workflows/storage-providers.yml.
Run live Postgres and PGVector tests locally:
docker run --name aim-pgvector-test \
-e POSTGRES_USER=test \
-e POSTGRES_PASSWORD=test \
-e POSTGRES_DB=memory \
-p 5432:5432 \
-d pgvector/pgvector:pg16
export AMH_TEST_POSTGRES_DSN="postgresql://test:test@127.0.0.1:5432/memory"
uv run pytest -q tests/integration/test_storage_features.py -k "postgres_live_integration_when_dsn_provided or postgres_schema_version or pgvector_live_integration_when_dsn_provided or runtime_postgres_pgvector_live_integration_when_dsn_provided"
docker rm -f aim-pgvector-test
Benchmarks and evaluation helpers:
uv run python -m memory.benchmarks.token_budget --iterations 200
uv run python -m memory.benchmarks.retrieval_quality
uv run python -m memory.benchmarks.cache_candidates --iterations 1000
Container CI verifies Hadolint, image build, packaged aim serve startup,
/ready, MCP initialize at /mcp/, arbitrary non-root UID behavior, and
writable runtime paths.
A Bruno integration layer provides black-box local and CI smoke tests for the live API/MCP surface against a running server and configured metadata/vector stores. The Bruno lane uploads HTML and JUnit artifacts and publishes JUnit results through GitHub Actions test summaries. Pytest CI jobs also emit JUnit XML artifacts for unit/integration, E2E, and storage lanes. See the Bruno integration test plan.
Project Structure
memory/
api/ FastAPI application and HTTP routes
backend/ metadata stores, vector stores, redaction, dry-run wrappers
ingestion/ schema validation and ingestion agents
interfaces/ MCP server implementation
schema/ conversation JSON schema
docs/ architecture notes, plans, and improvement documents
tests/ unit, integration, and end-to-end tests
Security Considerations
- Store real API keys outside committed configuration files.
- Use secure database connection strings and network access controls.
- Treat stored conversations as sensitive application data.
- Review retention, deletion, and redaction requirements before production use.
- Add authentication before exposing the API or MCP endpoint beyond localhost.
More Documentation
- Architecture
- Agent integration
- Roadmap
- Release, container, and docs publishing plan
- First release readiness plan
- Project promotion plan
- Bruno integration test plan
- Browser extension capture plan
- Observability, logging, and telemetry plan
- Recurring codebase cleanup plan
- Bearer/API-key auth plan
- Project workspace collaboration plan
- Improvement plans