server.memory_server is the Memory Engine HTTP service entry point provided by JiuwenMemory. It uses FastAPI to expose common LongTermMemory capabilities as REST APIs, allowing external systems to write, search, update, and manage long-term memory over HTTP.
The service is responsible for:
- assembling KV / DB / Vector Store backends from
.envat startup; - registering the embedding model and
LongTermMemoryengine configuration; - exposing APIs for message ingestion, memory update, variable management, semantic search, and paginated queries;
- providing optional Bearer Token authentication;
- requiring
MEMORY_API_KEYwhen binding to a non-local address, preventing unauthenticated network exposure.
The service supports two startup methods:
After installing JiuwenMemory via pip install, you can start the service directly with the memory-server command:
memory-serverThis command is defined in pyproject.toml under [project.scripts]:
[project.scripts]
memory-server = "jiuwen_memory.server.memory_server:main"Run from the project root:
python -m jiuwen_memory.server.memory_serverConfiguration note: LLM-related variables (
MODEL_PROVIDER,MODEL_NAME,API_KEY,API_BASE) and embedding-related variables (EMBED_MODEL_NAME,EMBED_API_KEY,EMBED_API_BASE) must be manually configured — their defaults are empty and the service will not function without them. Storage, bind address, and other settings have working defaults and can be overridden as needed.
Both methods are functionally identical. After startup, the service listens on:
127.0.0.1:8000
You can also configure the host and port with environment variables:
IP=127.0.0.1 PORT=8000 memory-serverOr using the source code method:
IP=127.0.0.1 PORT=8000 python -m jiuwen_memory.server.memory_serverSecurity note: If
IPis not127.*and notlocalhost, the service checks whetherMEMORY_API_KEYis configured. If it is missing, the process exits immediately to avoid exposing an unauthenticated API to the network.
On startup, the service loads .env files in the following priority order:
- First:
~/.jiuwenmemory/.env - Second:
.envin the current working directory
If neither .env file is found, the service automatically creates the ~/.jiuwenmemory/ directory and prompts the user to configure it.
⚠️ Important: Setting an environment variable to an empty string (e.g.,MEMORY_DATA_DIR=) will not trigger the default value —dotenvreads it as""instead ofNone. To use the default value, delete the line or comment it out (e.g.,# MEMORY_DATA_DIR=).
| Variable | Default | Description |
|---|---|---|
IP |
127.0.0.1 |
Service bind address. Non-local addresses require MEMORY_API_KEY. |
PORT |
8000 |
Service port. |
MEMORY_API_KEY |
empty string | API authentication key. If empty, authentication is disabled; this is recommended only for local development. |
MEMORY_DATA_DIR |
~/.jiuwenmemory/memory_data |
Default data directory for local SQLite, Chroma, Shelve, and related storage. Do not set this to an empty string — data would then be stored in the current working directory. |
| Variable | Default | Description |
|---|---|---|
MODEL_NAME |
empty string | LLM model name used for memory generation. Must be configured in .env. |
MODEL_PROVIDER |
empty string | LLM client provider. Must be configured in .env. |
API_KEY |
empty string | LLM API key. |
API_BASE |
empty string | LLM API base URL. |
EMBED_MODEL_NAME |
empty string | Embedding model name. Must be configured in .env. |
EMBED_API_KEY |
empty string | Embedding API key. Must be configured in .env. |
EMBED_API_BASE |
empty string | Embedding API endpoint. Must be configured in .env. |
memory_server assembles storage backends through server.store_factory.
| Variable | Default | Description |
|---|---|---|
DB_URL |
sqlite+aiosqlite:///{MEMORY_DATA_DIR}/sqlite_db.db |
SQLAlchemy async database URL. If omitted, local SQLite is used. |
KV_STORE_TYPE |
db |
KV Store type. Supported values: db / in_memory / shelve. |
KV_SHELVE_PATH |
{MEMORY_DATA_DIR}/shelve_kv |
Shelve file path when KV_STORE_TYPE=shelve. |
DB_STORE_TYPE |
default |
DB Store type. Supported values: default / gauss. |
INDEX_BACKEND |
simple |
Memory index backend. Supported values: simple (vector, KV+Vector) / file (markdown+SQLite). When file, long-term memories are persisted to markdown files; see FILE_MEMORY_DATA_DIR. |
FILE_MEMORY_DATA_DIR |
~/.jiuwenmemory/file_memory_data |
FileMemoryIndex data root directory. Only effective when INDEX_BACKEND=file. |
VECTOR_STORE_TYPE |
chroma |
Vector Store type. Supported values: chroma / milvus / elasticsearch / gauss. Under INDEX_BACKEND=file, the vector store is only used for middle-term memory / dreaming and may be left unconfigured. |
VECTOR_CHROMA_PERSIST_DIR |
MEMORY_DATA_DIR |
Chroma persistence directory. |
VECTOR_MILVUS_URI |
empty string | Milvus service URI. |
VECTOR_MILVUS_TOKEN |
empty string | Milvus token; optional. |
VECTOR_MILVUS_DATABASE |
default |
Milvus database name. |
VECTOR_ES_HOSTS |
empty string | Elasticsearch hosts, comma-separated. Use https://host:9200 to enable TLS, and configure the auth / SSL options below as needed. |
VECTOR_ES_INDEX_PREFIX |
agent_vector |
Elasticsearch index prefix. |
VECTOR_ES_USERNAME |
empty string | Elasticsearch username (basic_auth). Usually required when ES 8.x security features are enabled, e.g. elastic. |
VECTOR_ES_PASSWORD |
empty string | Elasticsearch password, paired with the username. |
VECTOR_ES_API_KEY |
empty string | Elasticsearch API Key auth. Mutually exclusive with username/password; left empty to disable. |
VECTOR_ES_VERIFY_CERTS |
empty string | Whether to verify TLS certificates. Set false to skip verification for self-signed certs; empty uses the default (verify). |
VECTOR_ES_CA_CERTS |
empty string | CA certificate path, commonly used for self-signed certs, e.g. /path/to/http_ca.crt. |
VECTOR_ES_CLIENT_CERT |
empty string | Mutual TLS (mTLS) client certificate path; left empty to disable. |
VECTOR_ES_CLIENT_KEY |
empty string | Mutual TLS (mTLS) client private key path; left empty to disable. |
VECTOR_GAUSS_HOST |
localhost |
Gauss vector store host. |
VECTOR_GAUSS_PORT |
5432 |
Gauss vector store port. |
VECTOR_GAUSS_DATABASE |
postgres |
Gauss vector store database name. |
VECTOR_GAUSS_USER |
postgres |
Gauss vector store user. |
VECTOR_GAUSS_PASSWORD |
empty string | Gauss vector store password. |
When INDEX_BACKEND=file, long-term memories are persisted as markdown files under FILE_MEMORY_DATA_DIR/memories/{user_id}/{scope_id}/{Type}.md (memories of the same type are merged into one file), with a SQLite memory.db holding the vector + FTS5 index. This mode has a few specifics worth knowing:
- Optional dependencies (graceful degradation when absent):
sqlite-vec— vector KNN search. If not installed, search falls back to a pure-Python cosine scan over stored embeddings.jieba— Chinese tokenization for FTS5 keyword recall. If not installed, FTS5 degrades tounicode61whitespace splitting (Chinese keyword recall becomes imprecise).watchdog— real-time sync on external.mdedits. If not installed, the watcher is a no-op and external edits are picked up lazily on the nextsearchvia hash-based dirty check (_ensure_synced).- Install all three at once:
pip install JiuwenMemory[file-index].
- Hybrid search: vector (sqlite-vec cosine, partitioned by
user_id+scope_id) + FTS5 (jieba + BM25), fused at 0.7/0.3 weight. When the embedding model is unavailable,searchdegrades to FTS-only keyword recall instead of returning empty. - Encryption tradeoff: the file backend stays plaintext by default (human-readable
.md); AES-GCM encryption at rest is only applied whencrypto_keyis configured. This differs from thesimplebackend, which encrypts by default. - Single-process only: no cross-process locking beyond SQLite WAL mode. Do not point multiple processes at the same
FILE_MEMORY_DATA_DIR. - V1 → V2 incompatibility: the V2 SQLite schema (added
path/start_line/end_linecolumns, afilestable) is not compatible with a V1memory.db. IfFILE_MEMORY_DATA_DIRpoints at a directory with a V1 DB, startup crashes withno such column: path. Point at a fresh directory or delete the oldmemory.db. V1's one-memory-per-file.mdlayout also cannot be read by V2.
memory_server uses a lightweight HTTP middleware for authentication:
GETrequests are always allowed, for endpoints such as/healthand/;- if
MEMORY_API_KEYis not configured, all requests are allowed; - if
MEMORY_API_KEYis configured, allPOST/PUT/DELETErequests must include:
Authorization: Bearer <MEMORY_API_KEY>If the header is missing or invalid, the service returns:
{
"detail": "Unauthorized: invalid or missing API key"
}with status code 401.
On startup, startup_event performs the following steps:
- Call
create_async_engine_from_env()to create the database engine; - Call
create_kv_store(engine)to create the KV Store; - Call
create_db_store(engine)to create the DB Store; - Call
create_vector_store()to create the Vector Store; - Create
APIEmbeddingfromEMBED_*environment variables; - Call
memory_engine.register_store(...)to register stores and the embedding model; - Create
MemoryEngineConfigfromMODEL_*/API_*environment variables; - Call
memory_engine.set_config(config)to complete engine configuration.
If initialization fails, the service logs the error and raises the exception, causing startup to fail.
Health check endpoint.
Response example:
{
"status": "healthy",
"message": "Memory Engine API is running"
}Root endpoint. Returns a welcome message and the list of exposed endpoints.
Response example:
{
"message": "Welcome to Memory Engine API",
"endpoints": [
"POST /add_messages/",
"POST /update_mem_by_id/",
"POST /update_variables/",
"POST /delete_variables/",
"POST /delete_mem_by_scope/",
"POST /get_variables/",
"POST /search_memory/",
"POST /search_user_history_summary/",
"POST /get_user_mem_by_page/",
"GET /health"
]
}Adds a list of conversation messages to the long-term memory engine.
The service converts request messages to BaseMessage objects and constructs an AgentMemoryConfig from the provided mem_variables and extraction switches, then calls LongTermMemory.add_messages(...).
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
messages |
list[dict[str, str]] |
Yes | - | Message list. Each item usually contains role and content. Missing values default to user / empty string. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID for business isolation. |
mem_variables |
list[MemVariable] |
No | [] |
Variable definitions to extract from conversations. Each element only requires name + description (other fields are optional with defaults). If omitted, no variables are extracted. |
enable_long_term_mem |
bool |
No | true |
Enable long-term memory extraction. |
enable_user_profile |
bool |
No | true |
Enable user profile memory extraction. |
enable_semantic_memory |
bool |
No | true |
Enable semantic memory extraction. |
enable_episodic_memory |
bool |
No | true |
Enable episodic memory extraction. |
enable_summary_memory |
bool |
No | true |
Enable summary memory extraction. |
mem_variables field details:
The MemVariable object defines variables to extract from conversations, with the following structure. Only name and description are required; the server fills in defaults for the rest before passing to the engine.
| Sub-field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
str |
Yes | - | Variable name. |
description |
str |
Yes | - | Variable description, used to guide LLM extraction. |
type |
str |
No | string |
Variable type. Only simple types are supported: string / boolean / integer / number. Array/Object and unknown values are rejected with 422 (variable extraction is a flat key/value structure and does not support nested types). |
required |
bool |
No | true |
Whether the variable is required. |
default |
any |
No | null |
Default value. |
Unknown fields are rejected with 422 (
extra='forbid'), so typos likedescriptonare surfaced instead of being silently ignored.
Request example (no mem_variables, basic memory extraction only):
curl -X POST http://127.0.0.1:8000/add_messages/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${MEMORY_API_KEY}" \
-d '{
"messages": [
{"role": "user", "content": "I like jasmine tea"},
{"role": "assistant", "content": "Got it, I will remember your preference."}
],
"user_id": "user_001",
"scope_id": "demo"
}'Request example (with mem_variables and extraction switches):
curl -X POST http://127.0.0.1:8000/add_messages/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${MEMORY_API_KEY}" \
-d '{
"messages": [
{"role": "user", "content": "I like jasmine tea and live in Shenzhen"},
{"role": "assistant", "content": "Noted."}
],
"user_id": "user_001",
"scope_id": "demo",
"mem_variables": [
{"name": "favorite_drink", "description": "The user's favorite drink"},
{"name": "city", "description": "The city where the user lives", "type": "string", "required": false}
],
"enable_user_profile": true,
"enable_summary_memory": false
}'Response example:
{
"status": "success",
"message": "Messages added successfully"
}Updates memory content by memory ID.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
mem_id |
str |
Yes | - | Memory ID to update. |
memory |
str |
Yes | - | Updated memory content. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
Request example:
{
"mem_id": "mem_123",
"memory": "The user likes jasmine tea",
"user_id": "user_001",
"scope_id": "demo"
}Response example:
{
"status": "success",
"message": "Memory mem_123 updated successfully"
}Updates user variable memories.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
variables |
dict[str, str] |
Yes | - | Mapping from variable names to variable values. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
Request example:
{
"variables": {
"favorite_drink": "jasmine tea",
"city": "Shenzhen"
},
"user_id": "user_001",
"scope_id": "demo"
}Response example:
{
"status": "success",
"message": "Variables updated successfully"
}Deletes specified user variables.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
names |
list[str] |
Yes | - | Variable names to delete. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
Request example:
{
"names": ["favorite_drink", "city"],
"user_id": "user_001",
"scope_id": "demo"
}Response example:
{
"status": "success",
"deleted": ["favorite_drink", "city"]
}The
deletedfield directly passes through the return value ofLongTermMemory.delete_variables(...). Its actual structure depends on the underlying implementation.
Deletes all memories under the specified scope_id.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
scope_id |
str |
Yes | - | Scope ID to delete. |
Request example:
{
"scope_id": "demo"
}Response example:
{
"status": "success",
"deleted": 12
}The
deletedfield directly passes through the return value ofLongTermMemory.delete_mem_by_scope(...). Its actual structure depends on the underlying implementation.
Gets user variables.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
names |
list[str] |
No | null |
Variable names to query. If omitted, the returned range is determined by the underlying implementation. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
Request example:
{
"names": ["favorite_drink"],
"user_id": "user_001",
"scope_id": "demo"
}Response example:
{
"variables": {
"favorite_drink": "jasmine tea"
}
}Searches user long-term memories.
The service calls LongTermMemory.search_user_mem(...) and serializes results into a list containing mem_id, content, type, and score.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query |
str |
Yes | - | Search query. |
num |
int |
No | 10 |
Number of results to return. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
threshold |
float |
No | 0.3 |
Similarity threshold. |
Request example:
{
"query": "What tea does the user like?",
"num": 5,
"user_id": "user_001",
"scope_id": "demo",
"threshold": 0.3
}Response example:
{
"results": [
{
"mem_id": "mem_123",
"content": "The user likes jasmine tea",
"type": "user_profile",
"score": 0.86
}
]
}Searches user history summaries.
The service calls LongTermMemory.search_user_history_summary(...) and returns the same result structure as /search_memory/.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query |
str |
Yes | - | Search query. |
num |
int |
No | 10 |
Number of results to return. |
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
threshold |
float |
No | 0.3 |
Similarity threshold. |
Request example:
{
"query": "What drink preferences has the user recently discussed?",
"num": 5,
"user_id": "user_001",
"scope_id": "demo",
"threshold": 0.3
}Response example:
{
"results": [
{
"mem_id": "summary_123",
"content": "The user recently mentioned that they like jasmine tea.",
"type": "summary",
"score": 0.78
}
]
}Gets user memories by page.
memory_type is converted to the MemoryType enum. Unrecognized values fall back to MemoryType.UNKNOWN.
Request parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
user_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
User ID. |
scope_id |
str |
No | LongTermMemory.DEFAULT_VALUE |
Scope ID. |
page_size |
int |
No | 10 |
Page size. |
page_idx |
int |
No | 1 |
Page index, starting from 1. |
memory_type |
str |
No | UNKNOWN |
Memory type string corresponding to the MemoryType enum. |
Request example:
{
"user_id": "user_001",
"scope_id": "demo",
"page_size": 10,
"page_idx": 1,
"memory_type": "UNKNOWN"
}Response example:
{
"results": [
{
"mem_id": "mem_123",
"content": "The user likes jasmine tea",
"type": "user_profile"
}
],
"total": 1
}Except for authentication failures, business endpoint exceptions are converted to 500 responses in the following format:
{
"detail": "Error searching memory: <specific error message>"
}Different endpoints use different error prefixes, for example:
Error adding messages: ...Error updating memory: ...Error updating variables: ...Error deleting variables: ...Error deleting memory by scope: ...Error getting variables: ...Error searching memory: ...Error searching user history summary: ...Error getting user memory by page: ...
CLI command method:
MEMORY_API_KEY="dev-secret" \
MODEL_PROVIDER="xxxx" \
MODEL_NAME="xxxx" \
API_KEY="xxxx" \
API_BASE="xxxx" \
EMBED_MODEL_NAME="xxxx" \
EMBED_API_KEY="xxxx" \
EMBED_API_BASE="xxxx" \
memory-serverSource code method:
MEMORY_API_KEY="dev-secret" \
MODEL_PROVIDER="xxxx" \
MODEL_NAME="xxxx" \
API_KEY="xxxx" \
API_BASE="xxxx" \
EMBED_MODEL_NAME="xxxx" \
EMBED_API_KEY="xxxx" \
EMBED_API_BASE="xxxx" \
python -m jiuwen_memory.server.memory_serverThe recommended approach is to configure all environment variables in
~/.jiuwenmemory/.env, then simply runmemory-server. Replacexxxxin the examples above with the actual configuration values from your LLM/Embedding provider.
curl -X POST http://127.0.0.1:8000/add_messages/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dev-secret" \
-d '{
"messages": [
{"role": "user", "content": "I like jasmine tea"},
{"role": "assistant", "content": "I will remember that you like jasmine tea."}
],
"user_id": "user_001",
"scope_id": "demo"
}'curl -X POST http://127.0.0.1:8000/search_memory/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dev-secret" \
-d '{
"query": "What does the user like to drink?",
"num": 5,
"user_id": "user_001",
"scope_id": "demo",
"threshold": 0.3
}'By default (when MEMORY_DATA_DIR is not set or is commented out), all local storage data is stored under:
~/.jiuwenmemory/memory_data/
├── sqlite_db.db ← Shared SQLite database for DB Store + KV Store (db mode)
├── chroma.sqlite3 ← Chroma Vector Store index file
└── (UUID directories) ← Chroma collection data (one per scope)
| Storage type | Default location | Notes |
|---|---|---|
| DB Store + KV Store (db mode) | ~/.jiuwenmemory/memory_data/sqlite_db.db |
Shares the same SQLite file |
| Vector Store (Chroma) | ~/.jiuwenmemory/memory_data/ |
Chroma persistence directory |
| KV Store (in_memory) | In-process memory | Lost on restart |
| KV Store (shelve) | ~/.jiuwenmemory/memory_data/shelve_kv |
Local file |
When switching to remote backends (PostgreSQL, Milvus, Elasticsearch, etc.), data is stored on the corresponding server, not locally.
GET /healthandGET /do not require authentication. Other write, delete, and query endpoints require Bearer Token whenMEMORY_API_KEYis configured.- If
DB_URLis not configured, local SQLite is used. IfVECTOR_STORE_TYPEis not configured, Chroma is used and persisted underMEMORY_DATA_DIR. /add_messages/supportsmem_variablesfor specifying variable definitions andenable_*switches for controlling each type of memory extraction. When these fields are omitted, all memory extraction is enabled and no variables are extracted./search_memory/and/search_user_history_summary/return service-layer serialized results instead of exposing internal objects directly.- The
totalfield of/get_user_mem_by_page/is currently the length of the returned list in this response, not necessarily the total number of matching records in storage. - The
page_idxof/get_user_mem_by_page/starts from 1 (not 0).